Nasłuchuj zmian właściwości zależności

80

Czy jest sposób, aby posłuchać zmian a DependencyProperty? Chcę być powiadamiany i wykonywać pewne czynności, gdy wartość się zmieni, ale nie mogę użyć powiązania. To jest DependencyPropertyz innej klasy.

Rasto
źródło
Dlaczego mówisz, że nie możesz używać oprawy?
Robert Rossney,

Odpowiedzi:

59

Jeśli jest DependencyPropertyto osobna klasa, najłatwiejszym sposobem jest przypisanie do niej wartości i nasłuchiwanie zmian tej wartości.

Jeśli DP jest tym, który implementujesz we własnej klasie, możesz zarejestrować PropertyChangedCallback podczas tworzenia DependencyProperty. Możesz użyć tego do odsłuchania zmian w nieruchomości.

Jeśli pracujesz z podklasą, możesz użyć OverrideMetadata, aby dodać własną PropertyChangedCallbackdo DP, która zostanie wywołana zamiast jakiejkolwiek oryginalnej.

Reed Copsey
źródło
11
Zgodnie z MSDN i moim doświadczeniem, niektóre cechy (dostarczonych metadanych) ... Inne, takie jak PropertyChangedCallback, są łączone. Więc Twój własny PropertyChangedCallback zostanie wywołany oprócz istniejących wywołań zwrotnych, a nie zamiast .
Marcel Gosselin,
1
martwy link? czy jest to teraz msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx ?
Simon K.
Czy byłoby możliwe dodanie tego wyjaśnienia do odpowiedzi? Myślałem, że OverrideMetadata zastąpi wywołania zwrotne rodzica, a to powstrzymywało mnie przed użyciem.
nazwa użytkownika
1
Zgadzam się, nie jest to zbyt jasne: „najłatwiej jest przypisać do niej wartość i wysłuchać zmian tej wartości”. Przykład byłby bardzo pomocny
UuDdLrLrSs
154

Tej metody zdecydowanie brakuje:

DependencyPropertyDescriptor
    .FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
    .AddValueChanged(radioButton, (s,e) => { /* ... */ });
HB
źródło
67
Zachowaj ostrożność, ponieważ może to łatwo wprowadzić wycieki pamięci! Zawsze usuwaj program obsługi ponownie, używającdescriptor.RemoveValueChanged(...)
CodeMonkey,
7
zobacz szczegóły i alternatywne podejście (zdefiniuj nową właściwość zależności + powiązanie) na agsmith.wordpress.com/2008/04/07/...
Lu55
2
Działa to w przypadku WPF (do czego służy to pytanie). Jeśli wylądujesz tutaj, szukając rozwiązania dla sklepu Windows, musisz użyć sztuczki wiążącej. Znalazłem ten post na blogu, który może pomóc: blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/ ... Prawdopodobnie działa również z WPF (jak wspomniano w powyższej odpowiedzi).
Gordon
2
@Todd: Myślę, że wyciek jest odwrotny, widok może utrzymać twój model widoku przy życiu z powodu odniesienia do programu obsługi. Kiedy widok zbywa się, subskrypcja powinna i tak zniknąć. Myślę, że ludzie są trochę zbyt paranoiczni, jeśli chodzi o wycieki z programów obsługujących zdarzenia, zwykle nie stanowi to problemu.
HB
4
@HB W tym przypadku DependencyPropertyDescriptorma statyczną listę wszystkich programów obsługi w aplikacji, więc każdy obiekt, do którego odwołuje się program obsługi, będzie przeciekać. To nie działa jak zwykłe wydarzenie.
ghord
19

Napisałem tę klasę użytkową:

  • Daje DependencyPropertyChangedEventArgs ze starą i nową wartością.
  • Źródło jest przechowywane w słabym odwołaniu w powiązaniu.
  • Nie jestem pewien, czy ujawnienie Binding & BindingExpression jest dobrym pomysłem.
  • Brak przecieków.
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);
    }
}
Johan Larsson
źródło
jeśli powiązanie to OneWay, dlaczego ustawiasz UpdateSourceTrigger?
Maslow
6

Można to osiągnąć na wiele sposobów. Oto sposób na przekonwertowanie właściwości zależnej na obserwowalną, tak aby można ją było zasubskrybować za pomocą 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));
        });
    }
}

Stosowanie

Pamiętaj, aby pozbyć się subskrypcji, aby zapobiec wyciekom pamięci:

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);
    }
}
MovGP0
źródło
4

Możesz odziedziczyć Kontrolę, której próbujesz słuchać, a następnie mieć bezpośredni dostęp do:

protected void OnPropertyChanged(string name)

Brak ryzyka wycieku pamięci.

Nie bój się standardowych technik OO.

Todd
źródło
1

Jeśli tak jest, One hack. Możesz wprowadzić klasę statyczną z rozszerzeniem DependencyProperty. Twoja klasa źródłowa również wiąże się z tym DP, a Twoja klasa docelowa również wiąże się z DP.

Książę Ashitaka
źródło