【问题标题】:Listen to changes of dependency property监听依赖属性的变化
【发布时间】:2011-06-13 11:44:39
【问题描述】:

有什么方法可以监听DependencyProperty 的变化吗?我想在值更改但无法使用绑定时收到通知并执行一些操作。它是另一个类的DependencyProperty

【问题讨论】:

  • 为什么说不能使用绑定?

标签: c# .net wpf events dependency-properties


【解决方案1】:

如果它是一个单独类的DependencyProperty,最简单的方法是绑定一个值,然后监听该值的变化。

如果 DP 是您在自己的课程中实现的,那么您可以在创建 DependencyProperty 时使用 register a PropertyChangedCallback。您可以使用它来监听属性的变化。

如果您正在使用子类,您可以使用 OverrideMetadata 将您自己的 PropertyChangedCallback 添加到将被调用的 DP,而不是任何原始的。

【讨论】:

  • 根据MSDN 和我的经验,一些特征(提供的元数据)......其他的,比如 PropertyChangedCallback,被结合起来了。 所以你自己的 PropertyChangedCallback 会得到调用除了到现有的回调,而不是而不是
  • 是否可以在答案中添加此说明?我认为 OverrideMetadata 将替换父级的回调,这阻碍了我使用它。
  • 我同意,这不是很清楚:“最简单的方法是绑定一个值,然后监听该值的变化”。一个例子会很有帮助
【解决方案2】:

如果是这样的话,One hack。您可以使用DependencyProperty 引入静态类。您的源类也绑定到该 dp,而您的目标类也绑定到 DP。

【讨论】:

    【解决方案3】:

    这里肯定少了这个方法:

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

    【讨论】:

    • 对此要非常小心,因为它很容易引入内存泄漏!始终使用 descriptor.RemoveValueChanged(...) 再次删除处理程序
    • agsmith.wordpress.com/2008/04/07/…查看详细信息和另一种方法(定义新的依赖属性+绑定)
    • 这适用于 WPF(这就是这个问题的目的)。如果您在这里寻找 Windows 商店解决方案,则需要使用绑定技巧。发现这篇博文可能会有所帮助:blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… 可能也适用于 WPF(如上述答案中所述)。
    • @Todd:我认为泄漏是相反的,由于对处理程序的引用,视图可能会使您的视图模型保持活动状态。当视图正在处理时,订阅也应该消失。我认为人们对事件处理程序的泄漏有点过于偏执,通常这不是问题。
    • @H.B.在这种情况下,DependencyPropertyDescriptor 具有应用程序中所有处理程序的静态列表,因此处理程序中引用的每个对象都会泄漏。它不像普通事件那样工作。
    【解决方案4】:

    我写了这个实用类:

    • 它为 DependencyPropertyChangedEventArgs 提供了旧值和新值。
    • 源存储在绑定中的弱引用中。
    • 不确定公开 Binding 和 BindingExpression 是否是个好主意。
    • 没有泄漏。
    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);
        }
    }
    

    【讨论】:

    • 如果绑定是OneWay,为什么要设置UpdateSourceTrigger?
    【解决方案5】:

    您可以继承您尝试收听的控件,然后直接访问:

    protected void OnPropertyChanged(string name)
    

    没有内存泄漏的风险。

    不要害怕标准的 OO 技术。

    【讨论】:

      【解决方案6】:

      有多种方法可以实现这一点。这是一种将依赖属性转换为可观察属性的方法,以便可以使用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);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2017-05-16
        • 1970-01-01
        • 1970-01-01
        • 2023-02-08
        • 2017-04-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多