Слушайте изменения свойства зависимости


80

Есть ли способ прослушать изменения a DependencyProperty? Я хочу получать уведомления и выполнять некоторые действия при изменении значения, но я не могу использовать привязку. Это DependencyPropertyдругого класса.


Почему вы говорите, что не можете использовать привязку?
Роберт Россни

Ответы:


59

Если это DependencyPropertyотдельный класс, самый простой способ - привязать к нему значение и прослушивать изменения этого значения.

Если DP - это тот, который вы реализуете в своем собственном классе, вы можете зарегистрировать PropertyChangedCallback при создании DependencyProperty. Вы можете использовать это для прослушивания изменений свойства.

Если вы работаете с подклассом, вы можете использовать OverrideMetadata для добавления своего собственного PropertyChangedCallbackк DP, который будет вызываться вместо любого исходного.


11
Согласно MSDN и моему опыту, некоторые характеристики (предоставленных метаданных) ... Другие, такие как PropertyChangedCallback, объединены. Таким образом, ваш собственный PropertyChangedCallback будет вызываться в дополнение к существующим обратным вызовам, а не вместо .
Marcel Gosselin

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

Можно ли добавить это уточнение к ответу? Я думал, что OverrideMetadata заменит обратные вызовы родителя, и это удерживало меня от его использования.
имя пользователя

1
Я согласен, это не очень понятно: «самый простой способ - привязать к нему значение и прослушивать изменения этого значения». Пример был бы очень полезен
UuDdLrLrSs

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 (для чего этот вопрос). Если вы приземлитесь здесь в поисках решения для магазина Windows, вам нужно использовать трюк с привязкой. Нашел это сообщение в блоге, которое может помочь: 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?
Maslow

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

Вы можете унаследовать Control, который пытаетесь прослушать, а затем получить прямой доступ к:

protected void OnPropertyChanged(string name)

Нет риска утечки памяти.

Не бойтесь стандартных методов объектно-ориентированного программирования.


1

Если это так, One hack. Вы можете представить статический класс с расширением DependencyProperty. Ваш исходный класс также связывается с этим dp, а ваш целевой класс также связывается с DP.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.