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.
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.
Cette méthode est définitivement absente ici:
DependencyPropertyDescriptor
.FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
.AddValueChanged(radioButton, (s,e) => { /* ... */ });
J'ai écrit cette classe utilitaire:
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);
}
}
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);
}
}
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.
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.