Escuche los cambios de la propiedad de dependencia


80

¿Hay alguna forma de escuchar los cambios de a DependencyProperty? Quiero ser notificado y realizar algunas acciones cuando cambie el valor, pero no puedo usar el enlace. Es DependencyPropertyde otra clase.


¿Por qué dice que no puede utilizar la vinculación?
Robert Rossney

Respuestas:


59

Si es DependencyPropertyde una clase separada, la forma más fácil es vincularle un valor y escuchar los cambios en ese valor.

Si el DP es uno que está implementando en su propia clase, entonces puede registrar un PropertyChangedCallback cuando cree el DependencyProperty. Puede usar esto para escuchar los cambios de la propiedad.

Si está trabajando con una subclase, puede usar OverrideMetadata para agregar el suyo PropertyChangedCallbackal DP al que se llamará en lugar del original.


11
Según MSDN y mi experiencia, algunas características (de los metadatos suministrados) ... otras, como PropertyChangedCallback, se combinan. Por lo tanto, se llamará a su propia PropertyChangedCallback además de las devoluciones de llamada existentes, no en lugar de .
Marcel Gosselin


¿Sería posible agregar esta aclaración a la respuesta? Pensé que OverrideMetadata reemplazaría las devoluciones de llamada del padre, y esto me impedía usarlo.
nombre de usuario

1
Estoy de acuerdo, esto no está muy claro: "la forma más fácil es vincularle un valor y escuchar los cambios en ese valor". Un ejemplo sería muy útil
UuDdLrLrSs

154

Este método definitivamente falta aquí:

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

67
¡Tenga mucho cuidado con esto, ya que puede introducir fácilmente pérdidas de memoria! Siempre retire un controlador utilizando de nuevodescriptor.RemoveValueChanged(...)
codemonkey

7
ver detalles y un enfoque alternativo (definir nueva propiedad de dependencia + enlace) en agsmith.wordpress.com/2008/04/07/…
Lu55

2
Esto funciona para WPF (que es para lo que es esta pregunta). Si aterriza aquí buscando una solución de tienda de Windows, necesita usar el truco de encuadernación. Encontré esta publicación de blog que podría ayudar: blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… Probablemente también funcione con WPF (como se menciona en la respuesta anterior).
Gordon

2
@Todd: Creo que la fuga es al revés, la vista podría mantener vivo su modelo de vista debido a la referencia al controlador. Cuando la vista se deshaga, la suscripción también debería desaparecer de todos modos. Creo que la gente es un poco paranoica con las filtraciones de los controladores de eventos, por lo general no es un problema.
HB

4
@HB En este caso DependencyPropertyDescriptortiene una lista estática de todos los controladores en la aplicación, por lo que todos los objetos referenciados en el controlador se filtrarán. No funciona como evento común.
ghord

19

Escribí esta clase de utilidad:

  • Le da a DependencyPropertyChangedEventArgs un valor antiguo y nuevo.
  • La fuente se almacena en una referencia débil en el enlace.
  • No estoy seguro de si exponer Binding & BindingExpression es una buena idea.
  • Sin fugas.
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);
    }
}

si el enlace es OneWay, ¿por qué está configurando UpdateSourceTrigger?
Maslow

6

Hay varias formas de lograrlo. A continuación, se muestra una forma de convertir una propiedad dependiente en observable, de modo que se pueda suscribir mediante 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

Recuerde deshacerse de las suscripciones para evitar pérdidas de memoria:

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

Puede heredar el Control que está tratando de escuchar y luego tener acceso directo a:

protected void OnPropertyChanged(string name)

Sin riesgo de pérdida de memoria.

No tenga miedo de las técnicas OO estándar.


1

Si ese es el caso, One hack. Podría introducir una clase estática con un DependencyProperty. Su clase de origen también se une a ese dp y su clase de destino también se une al DP.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.