2011-01-21 8 views
62

¿Hay alguna manera de escuchar los cambios de un DependencyProperty? Quiero recibir notificaciones y realizar algunas acciones cuando el valor cambie, pero no puedo usar el enlace. Es un DependencyProperty de otra clase.Escucha los cambios de la propiedad de dependencia

+0

¿Por qué dices que no puedes usar encuadernación? –

Respuesta

49

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

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

Si está trabajando con una subclase, puede usar OverrideMetadata para agregar su propio PropertyChangedCallback al DP que se llamará en lugar del original.

+9

Según [MSDN] (http://msdn.microsoft.com/en-us/library/ms597491.aspx) y mi experiencia, _Algunas características (de los metadatos proporcionados) ... Otros, como PropertyChangedCallback, se combinan ._ Por lo tanto, su PropertyChangedCallback se llamará * además * a las devoluciones de llamada existentes, no * en lugar de *. –

+1

enlace muerto? ¿es https://msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx ahora? –

1

Si ese es el caso, un truco. Puede introducir una clase estática con DependencyProperty. Tu clase fuente también se une a ese dp y tu clase de destino también se une al DP.

129

Este método es, sin duda falta aquí:

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

¡Tenga mucho cuidado con esto ya que fácilmente puede introducir fugas de memoria! Elimine siempre un controlador de nuevo con 'descriptor.RemoveValueChanged (...)' – CodeMonkey

+7

vea los detalles y un enfoque alternativo (defina nueva propiedad de dependencia + enlace) en http://agsmith.wordpress.com/2008/04/07/propertydescriptor -addvaluechanged-alternative/ – Lu55

+2

Esto funciona para WPF (que es para lo que esta pregunta es). Si aterrizas aquí buscando una solución de Windows Store, debes usar el truco vinculante. Encontré esta publicación de blog que podría ayudar: http://blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/getting-change-notifications-from-any-dependency-property-in-windows-store- apps.aspx Probablemente también funciona con WPF (como se menciona en la respuesta anterior). – Gordon

14

Escribí esta clase de utilidad:

  • Da DependencyPropertyChangedEventArgs con edad & nuevo valor.
  • La fuente se almacena en una referencia débil en el enlace.
  • No estoy seguro si la exposición Encuadernación & BindingExpression es una buena idea.
  • Sin goteras.
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

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

+0

@Maslow No hay razón, solo ruido, lo actualizaré, gracias. –

2

Se puede heredar el control que está tratando de escuchar, y luego tienen acceso directo a:

protected void OnPropertyChanged(string name) 

No hay riesgo de pérdida de memoria.

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

3

Existen varias formas de lograr esto. Aquí es una forma de convertir una propiedad dependiente de un observable, de tal manera que se puede suscribir a la utilización 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)); 
     }); 
    } 
} 

Uso

olvide depositar 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); 
    } 
} 
Cuestiones relacionadas