Есть ли способ прослушать изменения a DependencyProperty? Я хочу получать уведомления и выполнять некоторые действия при изменении значения, но я не могу использовать привязку. Это DependencyPropertyдругого класса.
Почему вы говорите, что не можете использовать привязку?
Роберт Россни
Ответы:
59
Если это DependencyPropertyотдельный класс, самый простой способ - привязать к нему значение и прослушивать изменения этого значения.
Если DP - это тот, который вы реализуете в своем собственном классе, вы можете зарегистрировать PropertyChangedCallback при создании DependencyProperty. Вы можете использовать это для прослушивания изменений свойства.
Если вы работаете с подклассом, вы можете использовать OverrideMetadata для добавления своего собственного PropertyChangedCallbackк DP, который будет вызываться вместо любого исходного.
Согласно MSDN и моему опыту, некоторые характеристики (предоставленных метаданных) ... Другие, такие как PropertyChangedCallback, объединены. Таким образом, ваш собственный PropertyChangedCallback будет вызываться в дополнение к существующим обратным вызовам, а не вместо .
Можно ли добавить это уточнение к ответу? Я думал, что OverrideMetadata заменит обратные вызовы родителя, и это удерживало меня от его использования.
имя пользователя
1
Я согласен, это не очень понятно: «самый простой способ - привязать к нему значение и прослушивать изменения этого значения». Пример был бы очень полезен
Будьте очень осторожны с этим, так как это может привести к утечке памяти! Всегда удаляйте обработчик снова, используяdescriptor.RemoveValueChanged(...)
Это работает для WPF (для чего этот вопрос). Если вы приземлитесь здесь в поисках решения для магазина Windows, вам нужно использовать трюк с привязкой. Нашел это сообщение в блоге, которое может помочь: blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… Вероятно, также работает с WPF (как указано в ответе выше).
Гордон
2
@Todd: Я думаю, что утечка - это наоборот, представление может поддерживать вашу модель представления из-за ссылки на обработчик. Когда представление удаляется, подписка тоже должна исчезнуть. Я думаю, что люди слишком параноидально относятся к утечкам из обработчиков событий, обычно это не проблема.
HB
4
@HB В этом случае DependencyPropertyDescriptorесть статический список всех обработчиков в приложении, поэтому каждый объект, на который есть ссылка в обработчике, будет протекать. Это не работает как обычное мероприятие.
ghord
19
Я написал этот служебный класс:
Он дает DependencyPropertyChangedEventArgs со старым и новым значением.
Источник хранится в слабой ссылке в привязке.
Не уверен, что использование Binding & BindingExpression - хорошая идея.
Никаких утечек.
using System;
using System.Collections.Concurrent;
using System.Windows;
using System.Windows.Data;
publicsealedclassDependencyPropertyListener : DependencyObject, IDisposable
{
privatestaticreadonlyConcurrentDictionary<DependencyProperty, PropertyPath> Cache = new ConcurrentDictionary<DependencyProperty, PropertyPath>();
privatestaticreadonly DependencyProperty ProxyProperty = DependencyProperty.Register(
"Proxy",
typeof(object),
typeof(DependencyPropertyListener),
new PropertyMetadata(null, OnSourceChanged));
privatereadonly Action<DependencyPropertyChangedEventArgs> onChanged;
privatebool disposed;
publicDependencyPropertyListener(
DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
: this(source, Cache.GetOrAdd(property, x => new PropertyPath(x)), onChanged)
{
}
publicDependencyPropertyListener(
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;
}
publicevent EventHandler<DependencyPropertyChangedEventArgs> Changed;
public BindingExpression BindingExpression { get; }
public Binding Binding { get; }
public DependencyObject Source => (DependencyObject)this.Binding.Source;
publicvoidDispose()
{
if (this.disposed)
{
return;
}
this.disposed = true;
BindingOperations.ClearBinding(this, ProxyProperty);
}
privatestaticvoidOnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listener = (DependencyPropertyListener)d;
if (listener.disposed)
{
return;
}
listener.onChanged?.Invoke(e);
listener.OnChanged(e);
}
privatevoidOnChanged(DependencyPropertyChangedEventArgs e)
{
this.Changed?.Invoke(this, e);
}
}
если привязка OneWay, зачем вы устанавливаете UpdateSourceTrigger?
Maslow
6
Есть несколько способов добиться этого. Вот способ преобразовать зависимое свойство в наблюдаемое, чтобы на него можно было подписаться с помощью System.Reactive :
Если это так, One hack. Вы можете представить статический класс с расширением DependencyProperty. Ваш исходный класс также связывается с этим dp, а ваш целевой класс также связывается с DP.
Ответы:
Если это
DependencyProperty
отдельный класс, самый простой способ - привязать к нему значение и прослушивать изменения этого значения.Если DP - это тот, который вы реализуете в своем собственном классе, вы можете зарегистрировать PropertyChangedCallback при создании
DependencyProperty
. Вы можете использовать это для прослушивания изменений свойства.Если вы работаете с подклассом, вы можете использовать OverrideMetadata для добавления своего собственного
PropertyChangedCallback
к DP, который будет вызываться вместо любого исходного.источник
Этого метода здесь точно нет:
DependencyPropertyDescriptor .FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton)) .AddValueChanged(radioButton, (s,e) => { /* ... */ });
источник
descriptor.RemoveValueChanged(...)
DependencyPropertyDescriptor
есть статический список всех обработчиков в приложении, поэтому каждый объект, на который есть ссылка в обработчике, будет протекать. Это не работает как обычное мероприятие.Я написал этот служебный класс:
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); } }
источник
Есть несколько способов добиться этого. Вот способ преобразовать зависимое свойство в наблюдаемое, чтобы на него можно было подписаться с помощью 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)); }); } }
Применение
Не забудьте удалить подписки, чтобы предотвратить утечку памяти:
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); } }
источник
Вы можете унаследовать Control, который пытаетесь прослушать, а затем получить прямой доступ к:
protected void OnPropertyChanged(string name)
Нет риска утечки памяти.
Не бойтесь стандартных методов объектно-ориентированного программирования.
источник
Если это так, One hack. Вы можете представить статический класс с расширением
DependencyProperty
. Ваш исходный класс также связывается с этим dp, а ваш целевой класс также связывается с DP.источник