依存関係プロパティの変更を聞く


80

の変化を聞く方法はありますDependencyPropertyか?値が変更されたときに通知を受けていくつかのアクションを実行したいのですが、バインディングを使用できません。それはDependencyProperty別のクラスの。


なぜバインディングを使用できないと言うのですか?
ロバートロスニー2011年

回答:


59

それDependencyPropertyが別のクラスの場合、最も簡単な方法は、値をそれにバインドし、その値の変更をリッスンすることです。

DPが独自のクラスに実装している場合は、を作成するときにPropertyChangedCallback登録できますDependencyProperty。これを使用して、プロパティの変更をリッスンできます。

サブクラスを使用している場合は、OverrideMetadataを使用して、元のクラスのPropertyChangedCallback代わりに呼び出されるDPに独自のサブクラスを追加できます。


11
MSDNと私の経験によると、(提供されたメタデータの)いくつかの特性... PropertyChangedCallbackなどの他の特性が組み合わされています。したがって、独自のPropertyChangedCallbackは、の代わりでなく、既存のコールバックに加えて呼び出さます。
Marcel Gosselin 2011


この説明を答えに追加することは可能でしょうか?OverrideMetadataが親のコールバックを置き換えると思っていたので、これが原因で使用できなくなっていました。
ユーザー名

1
私は同意します。これはあまり明確ではありません。「最も簡単な方法は、値をそれにバインドし、その値の変更をリッスンすることです」。例としては、非常に参考になる
UuDdLrLrSs

154

この方法は間違いなくここにありません:

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

67
これはメモリリークを簡単に引き起こす可能性があるため、十分に注意してください。常にdescriptor.RemoveValueChanged(...)
CodeMonkey 2013年


2
これはWPFで機能します(これがこの質問の目的です)。Windowsストアソリューションを探してここに着陸した場合は、バインディングトリックを使用する必要があります。役立つかもしれないこのブログ投稿を見つけました:blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/…おそらくWPFでも動作します(上記の回答で述べたように)。
ゴードン

2
@Todd:リークは逆だと思います。ハンドラーへの参照により、ビューがビューモデルを存続させる可能性があります。ビューが破棄しているとき、サブスクリプションもとにかく消えるはずです。人々は私が思うにイベントハンドラーからのリークについて少し妄想的すぎます、通常それは問題ではありません。
HB

4
@HBこの場合DependencyPropertyDescriptor、アプリケーション内のすべてのハンドラーの静的リストがあるため、ハンドラーで参照されるすべてのオブジェクトがリークします。一般的なイベントのようには機能しません。
ghord 2017

19

私はこのユーティリティクラスを書きました:

  • DependencyPropertyChangedEventArgsに古い値と新しい値を与えます。
  • ソースはバインディングの弱参照に格納されます。
  • Bindingと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);
    }
}

バインディングがOneWayの場合、なぜUpdateSourceTriggerを設定するのですか?
マズロー2017年

6

これを実現する方法は複数あります。これは、System.Reactiveを使用してサブスクライブできるように、依存プロパティをobservableに変換する方法です。

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);
    }
}

4

聴こうとしているコントロールを継承して、次の場所に直接アクセスできます。

protected void OnPropertyChanged(string name)

メモリリークのリスクはありません。

標準のOOテクニックを恐れないでください。


1

その場合は、1つのハック。を使用して静的クラスを導入できDependencyPropertyます。ソースクラスもそのdpにバインドし、宛先クラスもDPにバインドします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.