2011-01-21 89 views
62

有沒有什麼方法可以聽取DependencyProperty的更改?我希望收到通知並在值發生變化時執行一些操作,但我無法使用綁定。這是另一類的DependencyProperty傾聽依賴屬性的更改

+0

爲什麼說你不能使用綁定? –

回答

49

如果是單獨類的DependencyProperty,最簡單的方法是將值綁定到該值,並監聽該值的更改。

如果DP是您在自己班級中實施的DP,那麼當您創建DependencyProperty時,您可以register a PropertyChangedCallback。您可以使用它來聆聽屬性的更改。

如果您使用的是子類,則可以使用OverrideMetadata將自己的PropertyChangedCallback添加到將被調用的DP,而不是任何原始調用。

+9

根據[MSDN](http://msdn.microsoft.com/en-us/library/ms597491.aspx)和我的經驗,_(所提供的元數據的)某些特徵...其他(如PropertyChangedCallback) ._因此,您自己的PropertyChangedCallback將*除現有回調以外的其他*調用*,而不是*。 –

+1

死鏈接?現在是https://msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx? –

1

如果是這樣的話,One hack。你可以引入一個帶有DependencyProperty的靜態類。您的源類也綁定到該dp,並且您的目標類也綁定到DP。

129

這種方法肯定是缺少在這裏:

DependencyPropertyDescriptor 
    .FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton)) 
    .AddValueChanged(radioButton, (s,e) => { /* ... */ }); 
+50

因爲它可以很容易地引入內存泄漏,所以要非常小心! 總是使用'descriptor.RemoveValueChanged(...)' – CodeMonkey

+7

再次刪除處理程序請參閱http://agsmith.wordpress.com/2008/04/07/propertydescriptor中的詳細信息和替代方法(定義新的依賴項屬性+綁定) -addvaluechanged-alternative/ – Lu55

+2

這適用於WPF(這是這個問題的目的)。如果您在此尋找Windows Store解決方案,則需要使用綁定技巧。發現這篇博文可能有所幫助:http://blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/getting-change-notifications-from-any-dependency-property-in-windows-store- apps.aspx可能也適用於WPF(如上面的答案中所述)。 – Gordon

14

我寫了這個工具類:

  • 它給老&新的價值DependencyPropertyChangedEventArgs。
  • 源被存儲在綁定中的弱引用中。
  • 不確定是否暴露綁定& BindingExpression是個好主意。
  • 沒有泄漏。
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); 
    } 
} 
+0

如果綁定是OneWay,爲什麼你要設置UpdateSourceTrigger? – Maslow

+0

@Maslow沒有理由,只是噪音,我會更新,謝謝。 –

2

你可以繼承你想要聽的控制,然後可以直接獲得:

protected void OnPropertyChanged(string name) 

內存泄漏的沒有風險。

不要害怕標準的面向對象技術。

3

有多種方法可以實現這一點。這裏是一個辦法依賴屬性轉換爲可觀察到的,例如,它可以訂閱到使用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); 
    } 
}