【发布时间】:2011-06-13 11:44:39
【问题描述】:
有什么方法可以监听DependencyProperty 的变化吗?我想在值更改但无法使用绑定时收到通知并执行一些操作。它是另一个类的DependencyProperty。
【问题讨论】:
-
为什么说不能使用绑定?
标签: c# .net wpf events dependency-properties
有什么方法可以监听DependencyProperty 的变化吗?我想在值更改但无法使用绑定时收到通知并执行一些操作。它是另一个类的DependencyProperty。
【问题讨论】:
标签: c# .net wpf events dependency-properties
如果它是一个单独类的DependencyProperty,最简单的方法是绑定一个值,然后监听该值的变化。
如果 DP 是您在自己的课程中实现的,那么您可以在创建 DependencyProperty 时使用 register a PropertyChangedCallback。您可以使用它来监听属性的变化。
如果您正在使用子类,您可以使用 OverrideMetadata 将您自己的 PropertyChangedCallback 添加到将被调用的 DP,而不是任何原始的。
【讨论】:
如果是这样的话,One hack。您可以使用DependencyProperty 引入静态类。您的源类也绑定到该 dp,而您的目标类也绑定到 DP。
【讨论】:
这里肯定少了这个方法:
DependencyPropertyDescriptor
.FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
.AddValueChanged(radioButton, (s,e) => { /* ... */ });
【讨论】:
descriptor.RemoveValueChanged(...) 再次删除处理程序
DependencyPropertyDescriptor 具有应用程序中所有处理程序的静态列表,因此处理程序中引用的每个对象都会泄漏。它不像普通事件那样工作。
我写了这个实用类:
using System;
using System.Collections.Concurrent;
using System.Windows;
using System.Windows.Data;
public sealed class DependencyPropertyListener : DependencyObject, IDisposable
{
private static readonly ConcurrentDictionary<DependencyProperty, PropertyPath> Cache = new ConcurrentDictionary<DependencyProperty, PropertyPath>();
private static readonly DependencyProperty ProxyProperty = DependencyProperty.Register(
"Proxy",
typeof(object),
typeof(DependencyPropertyListener),
new PropertyMetadata(null, OnSourceChanged));
private readonly Action<DependencyPropertyChangedEventArgs> onChanged;
private bool disposed;
public DependencyPropertyListener(
DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
: this(source, Cache.GetOrAdd(property, x => new PropertyPath(x)), onChanged)
{
}
public DependencyPropertyListener(
DependencyObject source,
PropertyPath property,
Action<DependencyPropertyChangedEventArgs> onChanged)
{
this.Binding = new Binding
{
Source = source,
Path = property,
Mode = BindingMode.OneWay,
};
this.BindingExpression = (BindingExpression)BindingOperations.SetBinding(this, ProxyProperty, this.Binding);
this.onChanged = onChanged;
}
public event EventHandler<DependencyPropertyChangedEventArgs> Changed;
public BindingExpression BindingExpression { get; }
public Binding Binding { get; }
public DependencyObject Source => (DependencyObject)this.Binding.Source;
public void Dispose()
{
if (this.disposed)
{
return;
}
this.disposed = true;
BindingOperations.ClearBinding(this, ProxyProperty);
}
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listener = (DependencyPropertyListener)d;
if (listener.disposed)
{
return;
}
listener.onChanged?.Invoke(e);
listener.OnChanged(e);
}
private void OnChanged(DependencyPropertyChangedEventArgs e)
{
this.Changed?.Invoke(this, e);
}
}
using System;
using System.Windows;
public static class Observe
{
public static IDisposable PropertyChanged(
this DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
{
return new DependencyPropertyListener(source, property, onChanged);
}
}
【讨论】:
您可以继承您尝试收听的控件,然后直接访问:
protected void OnPropertyChanged(string name)
没有内存泄漏的风险。
不要害怕标准的 OO 技术。
【讨论】:
有多种方法可以实现这一点。这是一种将依赖属性转换为可观察属性的方法,以便可以使用System.Reactive 订阅它:
public static class DependencyObjectExtensions
{
public static IObservable<EventArgs> Observe<T>(this T component, DependencyProperty dependencyProperty)
where T:DependencyObject
{
return Observable.Create<EventArgs>(observer =>
{
EventHandler update = (sender, args) => observer.OnNext(args);
var property = DependencyPropertyDescriptor.FromProperty(dependencyProperty, typeof(T));
property.AddValueChanged(component, update);
return Disposable.Create(() => property.RemoveValueChanged(component, update));
});
}
}
用法
记得释放订阅以防止内存泄漏:
public partial sealed class MyControl : UserControl, IDisposable
{
public MyControl()
{
InitializeComponent();
// this is the interesting part
var subscription = this.Observe(MyProperty)
.Subscribe(args => { /* ... */}));
// the rest of the class is infrastructure for proper disposing
Subscriptions.Add(subscription);
Dispatcher.ShutdownStarted += DispatcherOnShutdownStarted;
}
private IList<IDisposable> Subscriptions { get; } = new List<IDisposable>();
private void DispatcherOnShutdownStarted(object sender, EventArgs eventArgs)
{
Dispose();
}
Dispose(){
Dispose(true);
}
~MyClass(){
Dispose(false);
}
bool _isDisposed;
void Dispose(bool isDisposing)
{
if(_disposed) return;
foreach(var subscription in Subscriptions)
{
subscription?.Dispose();
}
_isDisposed = true;
if(isDisposing) GC.SupressFinalize(this);
}
}
【讨论】: