【问题标题】:WPF custom DependencyProperty notify changesWPF 自定义 DependencyProperty 通知更改
【发布时间】:2011-02-20 19:27:24
【问题描述】:

我有一个名为 MyComponent 的类,它有一个名为 BackgroundProperty 的 DependencyProperty。

public class MyComponent
{
    public MyBackground Background
    {
        get { return (MyBackground)GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }
    public static readonly DependencyProperty BackgroundProperty =
        DependencyProperty.Register("Background", typeof(MyBackground),
            typeof(MyComponent), new FrameworkPropertyMetadata(default(MyBackground), new PropertyChangedCallback(OnPropertyChanged)));
}

MyBackground 是一个派生自 DependencyObject 的类,它有一些 DependencyProperties。

public class MyBackground : DependencyObject
{
    public Color BaseColor
    {
        set { SetValue(BaseColorProperty, value); }
        get { return (Color)GetValue(BaseColorProperty); }
    }
    public static readonly DependencyProperty BaseColorProperty =
        DependencyProperty.Register("BaseColor", typeof(Color),
            typeof(MyBackground ), new UIPropertyMetadata(Colors.White));

    [...]
}

现在,我想要的是当 MyBackground 中的属性发生更改时,通知 MyComponent MyBackground 已更改,并调用名为 OnPropertyChanged 的​​ PropertyChangedCallback。

【问题讨论】:

  • 我有点困惑你为什么需要它。通常情况正好相反,DP 用于绑定,当它们发生变化时,您想通知 DP。为什么你需要反过来呢?
  • 你是什么意思这是倒退@Omribitan?这是标准的 WPF。如果我修改依赖属性的值,绑定到该属性的所有东西都会立即知道它。这就是依赖属性的用途——WPF 的数据绑定就是建立在这个概念之上的。
  • @BrainSlugs83 想象一个控件的 Visibility 绑定到 ViewModel 的类上的一个属性,我们称之为IsVisibileVisibility 是 DP,IsVisibile 是一个简单的属性。通常发生的情况是,当 IsVisible 发生更改时,您希望通知 UI(主要是使用 INotifyPropertyChanged)让 DP 知道它的值已更改,而不是相反...

标签: wpf dependency-properties notify dependencyobject


【解决方案1】:

这是我为 WPF 编写的一个小型静态扩展方法类——它允许您注册一个 EventHandler 或一个 Action 回调来更改任何 DependencyObject 上的任何 DependencyProperty。不需要更改依赖对象。

它还可以防止递归(即,如果您在回调期间更改相同的属性等)

它利用 @ScottBilas 链接到的 DependencyPropertyDescriptor。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;

namespace BrainSlugs83.Writes.Too.Much.Code
{
    public static class WpfExtensions
    {
        public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, Action<T> callback) where T : DependencyObject
        {
            if (callback != null)
            {
                obj.OnPropertyChanged(prop, new EventHandler((o, e) =>
                {
                    callback((T)o);
                }));
            }
        }

        public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, EventHandler handler) where T : DependencyObject
        {
            var descriptor = DependencyPropertyDescriptor.FromProperty(prop, typeof(T));
            descriptor.AddValueChanged(obj, new EventHandler((o, e) =>
            {
                if (handler != null)
                {
                    if (o == null) { handler(o, e); }
                    else
                    {
                        lock (PreventRecursions)
                        {
                            if (IsRecursing(obj, prop)) { return; }
                            SetIsRecursing(obj, prop, true);
                        }

                        try
                        {
                            handler(o, e);
                        }
                        finally
                        {
                            SetIsRecursing(obj, prop, false);
                        }
                    }
                }
            }));
        }

        #region OnPropertyChanged Recursion Prevention

        private static readonly Dictionary<object, List<DependencyProperty>> PreventRecursions = new Dictionary<object, List<DependencyProperty>>();

        private static bool IsRecursing(object obj, DependencyProperty prop)
        {
            lock (PreventRecursions)
            {
                List<DependencyProperty> propList = null;
                if (PreventRecursions.ContainsKey(obj))
                {
                    propList = PreventRecursions[obj];
                }

                return propList == null ? false : propList.Contains(prop);
            }
        }

        private static void SetIsRecursing(object obj, DependencyProperty prop, bool value)
        {
            lock (PreventRecursions)
            {
                List<DependencyProperty> propList = null;
                if (PreventRecursions.ContainsKey(obj))
                {
                    propList = PreventRecursions[obj];
                }

                if (propList == null)
                {
                    if (!value) { return; }

                    propList = PreventRecursions[obj] = new List<DependencyProperty>();
                }

                if (value)
                {
                    if (!propList.Contains(prop))
                    {
                        propList.Add(prop);
                    }
                }
                else
                {
                    while (propList.Contains(prop))
                    {
                        propList.Remove(prop);
                    }

                    if (!propList.Any())
                    {
                        propList = PreventRecursions[obj] = null;
                    }
                }
            }
        }

        #endregion

        public static bool IsInDesignMode(this DependencyObject obj)
        {
            try
            {
                return DesignerProperties.GetIsInDesignMode(obj);
            }
            catch { /* do nothing */ }

            return false;
        }
    }
}

【讨论】:

    【解决方案2】:

    执行您所描述的一种方法是从 Freezable 派生而不是 DependencyObject。当 Freezable 的属性更改时,将调用任何引用该 Freezable 的 DO 的 PropertyChangedCallback,因此您的 MyComponent 的 Background 属性的回调。在这种情况下, e.OldValue 和 e.NewValue 将是相同的引用。在 WPF 内部,事件 args 上有一些标志,表明它是子对象更改。

    这就是框架为画笔之类的东西所做的事情,这样如果 SolidColorBrush 的 Color 属性发生更改,元素就会失效。如果一个对象永远不会改变(或者你想让它成为线程安全的),那么可以冻结该对象(即使其不可变)。

    顺便说一句,我可能会避免使用背景作为属性的名称。大多数开发人员会假设它是 Brush 类型,因为框架在其多个元素(例如控件、边框)上用于该命名属性。

    【讨论】:

      【解决方案3】:

      请耐心等待,因为您似乎正试图违背 WPF 的原则。由于您似乎正在编写与显示逻辑相关的代码,因此获取相关DependencyObjects 以相互交互的典型方法是通过绑定。

      例如,如果MyComponent 是某种控件,并且它在其ControlTemplate 中使用Background 属性,则您将使用引用Background 属性和任何重要子属性的TemplateBinding属性。

      由于 1) 您可能已经知道,并且 2) 您没有使用模板或没有可用模板,因此您可以在代码中设置绑定,以便对 Background 属性的更改做出反应.如果您提供有关 OnPropertyChanged 方法的更多详细信息,我可以提供一些示例代码。

      【讨论】:

        【解决方案4】:

        听起来您想使用 DependencyPropertyDescriptor 和 AddValueChanged。

        这是一篇关于它的文章:http://www.codeproject.com/Articles/34741/Change-Notification-for-Dependency-Properties.aspx

        ..可能还有更好的实现:http://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/

        【讨论】:

          猜你喜欢
          • 2012-10-08
          • 1970-01-01
          • 2013-01-20
          • 1970-01-01
          • 2011-08-06
          • 1970-01-01
          • 2010-11-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多