web-dev-qa-db-fra.com

Écoutez les changements de propriété de dépendance

Existe-t-il un moyen d'écouter les changements d'un DependencyProperty? Je souhaite être averti et effectuer certaines actions lorsque la valeur change, mais je ne peux pas utiliser la liaison. C'est un DependencyProperty d'une autre classe.

74
Rasto

S'il s'agit d'un DependencyProperty d'une classe distincte, le moyen le plus simple consiste à lui associer une valeur et à écouter les modifications apportées à cette valeur.

Si le DP est celui que vous implémentez dans votre propre classe, vous pouvez alors enregistrer un PropertyChangedCallback lorsque vous créez le DependencyProperty. Vous pouvez l'utiliser pour écouter les modifications de la propriété.

Si vous travaillez avec une sous-classe, vous pouvez utiliser OverrideMetadata pour ajouter votre propre PropertyChangedCallback au DP qui sera appelé à la place de tout autre original.

54
Reed Copsey

Cette méthode est définitivement absente ici:

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

J'ai écrit cette classe utilitaire:

  • Il donne à DependencyPropertyChangedEventArgs une ancienne et une nouvelle valeur.
  • La source est stockée dans une référence faible dans la liaison.
  • Je ne sais pas si exposer Binding & BindingExpression est une bonne idée.
  • Pas de fuites.
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);
    }
}
17
Johan Larsson

Il existe plusieurs façons d'y parvenir. Voici un moyen de convertir une propriété dépendante en une observable, de telle sorte qu'elle puisse être souscrite à l'aide de 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));
        });
    }
}

Utilisation

N'oubliez pas de supprimer les abonnements pour éviter les fuites de mémoire:

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

Vous pouvez hériter du contrôle que vous essayez d'écouter, puis avoir un accès direct à:

protected void OnPropertyChanged(string name)

Aucun risque de fuite de mémoire.

N'ayez pas peur des techniques standard OO.

4
Todd

Si tel est le cas, One hack. Vous pouvez introduire une classe statique avec un DependencyProperty. Votre classe source se lie également à ce dp et votre classe de destination se lie également au DP.

1
Prince Ashitaka