Hören Sie sich Änderungen der Abhängigkeitseigenschaft an

80

Gibt es eine Möglichkeit, Änderungen von a zu hören DependencyProperty? Ich möchte benachrichtigt werden und einige Aktionen ausführen, wenn sich der Wert ändert, aber ich kann keine Bindung verwenden. Es gehört zu einer DependencyPropertyanderen Klasse.

Rasto
quelle
Warum kannst du keine Bindung verwenden?
Robert Rossney

Antworten:

59

Wenn es sich DependencyPropertyum eine separate Klasse handelt, ist es am einfachsten, einen Wert daran zu binden und Änderungen an diesem Wert abzuhören.

Wenn es sich bei dem DP um einen DP handelt, den Sie in Ihrer eigenen Klasse implementieren, können Sie beim Erstellen des einen PropertyChangedCallback registrierenDependencyProperty . Sie können dies verwenden, um Änderungen der Eigenschaft abzuhören.

Wenn Sie mit einer Unterklasse arbeiten, können Sie OverrideMetadata verwenden , um PropertyChangedCallbackdem DP einen eigenen hinzuzufügen , der anstelle eines ursprünglichen aufgerufen wird.

Reed Copsey
quelle
11
Laut MSDN und meiner Erfahrung werden einige Merkmale (der bereitgestellten Metadaten) ... Andere, wie z. B. PropertyChangedCallback, kombiniert. Ihr eigener PropertyChangedCallback wird also zusätzlich zu den vorhandenen Rückrufen aufgerufen , nicht stattdessen .
Marcel Gosselin
1
toter Link? ist es jetzt msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx ?
Simon K.
Wäre es möglich, diese Klarstellung der Antwort hinzuzufügen? Ich dachte, dass die OverrideMetadata die Rückrufe des übergeordneten Elements ersetzen würden, und dies hielt mich davon ab, sie zu verwenden.
Benutzername
1
Ich stimme zu, dies ist nicht sehr klar: "Der einfachste Weg ist, einen Wert daran zu binden und Änderungen an diesem Wert zu hören." Ein Beispiel wäre sehr hilfreich
UuDdLrLrSs
153

Diese Methode fehlt hier definitiv:

DependencyPropertyDescriptor
    .FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
    .AddValueChanged(radioButton, (s,e) => { /* ... */ });
HB
quelle
67
Seien Sie dabei sehr vorsichtig, da dies leicht zu Speicherlecks führen kann! Entfernen Sie einen Handler immer wieder mitdescriptor.RemoveValueChanged(...)
CodeMonkey
7
Details und einen alternativen Ansatz (neue Abhängigkeitseigenschaft + Bindung definieren) finden Sie unter agsmith.wordpress.com/2008/04/07/…
Lu55
2
Dies funktioniert für WPF (wofür diese Frage ist). Wenn Sie hier landen und nach einer Windows Store-Lösung suchen, müssen Sie den Bindungstrick verwenden. Fand diesen Blog-Beitrag, der helfen könnte: blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… Funktioniert wahrscheinlich auch mit WPF (wie in der obigen Antwort erwähnt).
Gordon
2
@Todd: Ich denke, das Leck ist umgekehrt. Die Ansicht könnte Ihr Ansichtsmodell aufgrund des Verweises auf den Handler am Leben erhalten. Wenn die Ansicht verfügbar ist, sollte das Abonnement ohnehin ebenfalls verschwinden. Die Leute sind ein bisschen zu paranoid in Bezug auf Lecks von Event-Handlern, denke ich, normalerweise ist das kein Problem.
HB
4
@HB In diesem Fall DependencyPropertyDescriptorgibt es eine statische Liste aller Handler in der Anwendung, sodass jedes Objekt, auf das im Handler verwiesen wird, ausläuft. Es funktioniert nicht wie ein gewöhnliches Ereignis.
Ghord
19

Ich habe diese Utility-Klasse geschrieben:

  • Es gibt DependencyPropertyChangedEventArgs mit altem und neuem Wert.
  • Die Quelle wird in einer schwachen Referenz in der Bindung gespeichert.
  • Ich bin mir nicht sicher, ob es eine gute Idee ist, Binding & BindingExpression verfügbar zu machen.
  • Keine Lecks.
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
quelle
Wenn die Bindung OneWay ist, warum setzen Sie UpdateSourceTrigger?
Maslow
6

Es gibt mehrere Möglichkeiten, dies zu erreichen. Hier ist eine Möglichkeit, eine abhängige Eigenschaft in eine beobachtbare Eigenschaft zu konvertieren, sodass sie mit System.Reactive abonniert werden kann :

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

Verwendung

Denken Sie daran, die Abonnements zu entsorgen, um Speicherverluste zu vermeiden:

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
quelle
4

Sie könnten das Steuerelement erben, das Sie abhören möchten, und dann direkten Zugriff auf Folgendes haben:

protected void OnPropertyChanged(string name)

Kein Risiko eines Speicherverlusts.

Haben Sie keine Angst vor Standard-OO-Techniken.

Todd
quelle
1

Wenn das der Fall ist, ein Hack. Sie könnten eine statische Klasse mit einem einführen DependencyProperty. Ihre Quellklasse bindet auch an diesen dp und Ihre Zielklasse bindet auch an den DP.

Prinz Ashitaka
quelle