Прослухайте зміни властивості залежності


80

Чи є спосіб прослухати зміни DependencyProperty? Я хочу отримати повідомлення та виконати деякі дії, коли значення змінюється, але я не можу використовувати прив'язку. Це DependencyPropertyінший клас.


Чому ви говорите, що не можете використовувати прив'язку?
Роберт Росней,

Відповіді:


59

Якщо це DependencyPropertyокремий клас, найпростіший спосіб - прив’язати до нього значення і прослухати зміни щодо цього значення.

Якщо DP - це той, який ви впроваджуєте у своєму власному класі, тоді ви можете зареєструвати PropertyChangedCallback під час створення DependencyProperty. Ви можете використовувати це для прослуховування змін властивості.

Якщо ви працюєте з підкласом, ви можете використовувати OverrideMetadata, щоб додати свій власний PropertyChangedCallbackдо DP, який буде викликаний замість будь-якого оригінального.


11
Згідно з MSDN та моїм досвідом, Деякі характеристики (наданих метаданих) ... Інші, такі як PropertyChangedCallback, поєднуються. Отже, ваш власний PropertyChangedCallback буде викликаний на додаток до існуючих зворотних викликів, а не замість .
Марсель Госселін,

1
мертва ланка? це msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx зараз?
Саймон К.

Чи можна було б додати це пояснення до відповіді? Я думав, що OverrideMetadata замінить зворотні виклики батьків, і це стримувало мене від використання.
ім'я користувача

1
Я згоден, це не дуже зрозуміло: "найпростіший спосіб - прив'язати до нього значення і прослухати зміни щодо цього значення". Приклад може бути дуже корисним
UuDdLrLrS

154

Цього методу тут точно не вистачає:

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

67
Будьте дуже обережні з цим, оскільки це може легко призвести до витоків пам'яті! Завжди видаляйте обробник ще раз, використовуючиdescriptor.RemoveValueChanged(...)
CodeMonkey

7
див. деталі та альтернативний підхід (визначити нову властивість залежності + прив'язка) на agsmith.wordpress.com/2008/04/07/…
Lu55

2
Це працює для WPF (для чого це питання). Якщо ви приземляєтесь тут, шукаючи рішення для магазину вікон, вам потрібно скористатися прив’язувальним трюком. Знайшов цей допис у блозі, який може допомогти: blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… Можливо, він також працює з WPF (як зазначено у відповіді вище).
Гордон

2
@Todd: Я думаю, що витік є навпаки, подання може зберегти вашу модель перегляду живою через посилання на обробник. Коли подання розпоряджається, підписка в будь-якому випадку також повинна зникнути. Думаю, люди занадто параноїчні щодо витоків з обробників подій, як правило, це не проблема.
HB

4
@HB У цьому випадку DependencyPropertyDescriptorмає статичний список усіх обробників у застосунку, тому кожен об'єкт, на який посилається в обробнику, буде просочуватися. Це не працює як звичайна подія.
ghord

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?
Маслоу

6

Цього можна досягти кількома способами. Ось спосіб перетворити залежне властивість на спостережуване, так що на нього можна підписатися за допомогою 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);
    }
}

4

Ви можете успадкувати елемент керування, який ви намагаєтеся слухати, а потім мати прямий доступ до:

protected void OnPropertyChanged(string name)

Немає ризику витоку пам'яті.

Не бійтеся стандартних методів ОО.


1

Якщо це так, один хак. Ви можете ввести статичний клас з a DependencyProperty. Ви вихідний клас також прив'язується до цього dp, а ваш цільовий клас також прив'язується до DP.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.