83 votes

Écouter les changements de propriété de la dépendance

Existe-t-il un moyen d'écouter les modifications d'une DependencyProperty ? Je veux être notifié et effectuer certaines actions lorsque la valeur change mais je ne peux pas utiliser la liaison. Il s'agit d'un DependencyProperty d'une autre classe.

0 votes

Pourquoi dites-vous que vous ne pouvez pas utiliser la reliure ?

161voto

H.B. Points 76352

Cette méthode est définitivement absente ici :

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

Attention : Parce que DependencyPropertyDescriptor possède une liste statique de tous les gestionnaires de l'application ; chaque objet référencé dans ces gestionnaires fuira si le gestionnaire n'est pas supprimé. (Cela ne fonctionne pas comme les événements communs sur les objets d'instance).

Il faut toujours retirer un gestionnaire en utilisant descriptor.RemoveValueChanged(...) .

71 votes

Soyez très prudent avec cela car cela peut facilement introduire des fuites de mémoire ! Supprimez toujours un gestionnaire en utilisant descriptor.RemoveValueChanged(...)

7 votes

Voir les détails et une approche alternative (définir une nouvelle propriété de dépendance + liaison) à l'adresse suivante agsmith.wordpress.com/2008/04/07/

2 votes

Cela fonctionne pour WPF (qui est l'objet de cette question). Si vous atterrissez ici à la recherche d'une solution pour Windows Store, vous devez utiliser l'astuce de liaison. J'ai trouvé cet article de blog qui pourrait vous aider : blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/ Cela fonctionne probablement aussi avec WPF (comme mentionné dans la réponse ci-dessus).

63voto

Reed Copsey Points 315315

Si c'est un DependencyProperty d'une classe distincte, le moyen le plus simple est de lui associer une valeur et d'écouter les modifications de cette valeur.

Si le DP est celui que vous implémentez dans votre propre classe, alors vous pouvez 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 la fonction Remplacer les métadonnées pour ajouter votre propre PropertyChangedCallback au DP qui sera appelé à la place de celui d'origine.

19voto

Johan Larsson Points 4405

J'ai écrit cette classe utilitaire :

  • Il donne des DependencyPropertyChangedEventArgs avec l'ancienne et la nouvelle valeur.
  • La source est stockée dans une référence faible dans la reliure.
  • Je ne suis pas sûr que l'exposition de Binding & BindingExpression soit 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);
    }
}

0 votes

Si la liaison est OneWay, pourquoi définissez-vous UpdateSourceTrigger ?

6voto

MovGP0 Points 77

Il existe plusieurs façons d'y parvenir. Voici une façon de convertir une propriété dépendante en un observable, de sorte qu'on puisse y souscrire en utilisant 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));
        });
    }
}

Uso

N'oubliez pas de disposer des 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);
    }
}

5voto

Todd Points 2386

Vous pourriez hériter du Contrôleur que vous essayez d'écouter, et ainsi avoir un accès direct à celui-ci :

protected void OnPropertyChanged(string name)

Aucun risque de fuite de mémoire.

N'ayez pas peur des techniques OO standard.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X