WPF 依赖属性 DependencyProperty

1.依赖属性介绍

在WPF中使用了更高级的依赖项属性替换了.net中的属性。依赖属性具有一些更高效的保存机制,同进支持附加功能,如 更改通知(Change Notification)以及属性值继承(在元素树中向下传递属性值)。依赖属性同样还是WPF中Animation,Binding,Style的重要基础。.

2.依赖属性定义

注意:只能为依赖对象(继承自DependencyObject)添加依赖属性。WPF中基础结构的关键部分中大部分都间接继承自DependencyObject类。

例子:

WPF中最常见的属性之一就是Margin属性。它在FramewordElement类中被定义,所有元素都共享自该属性。下面是FrameworkElement类的源函数

public class FrameworkElement:UIElement
{
        [CommonDependencyProperty]
        public static readonly DependencyProperty MarginProperty;
}

约定在定义依赖属性时在普通属性的末尾加上单词“Property”。字段的定义使用Public static readonly。这意味着只能在FrameworkElement类的静态构造函数中对其进行设置。

3.依赖属性注册

注意DependencyProperty类没有公有的构造函数。故只能使用DependencyProperty.Register()方法来创建DependencyProperty的实例。DependencyProperty.Register()同样是一个静态的方法。下面是该方法的定义:

        //
        // 摘要:
        //     使用指定的属性名称属性类型、所有者类型、属性元数据和属性的值验证回调来注册依赖项属性。
        //
        // 参数:
        //   name:
        //     要注册的依赖项对象的名称。
        //
        //   propertyType:
        //     属性的类型。
        //
        //   ownerType:
        //     正注册依赖项对象的所有者类型。
        //
        //   typeMetadata:
        //     依赖项对象的属性元数据。(可选)
        //
        //   validateValueCallback:
        //     对回调的引用,除了典型的类型验证之外,该引用还应执行依赖项对象值的任何自定义验证。(可选)
        //
        // 返回结果:
        //     一个依赖项对象标识符,应使用它在您的类中设置 publicstaticreadonly 字段的值。
        //然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。
        public static DependencyProperty Register(
                                                   string name,Type propertyType,Type ownerType,PropertyMetadata typeMetadata,ValidateValueCallback validateValueCallback);

注册一个依赖属性主要有两个步骤

1.创建一个FrameworkPropertyMetadata对象,该对象指示了希望通过依赖属性来使用什么服务(如支持动画,数据绑定等)。

FrameworkPropertyMetadata的主要属性如下:

        public bool AffectsArrange { get; set; }
        public bool AffectsMeasure { get; set; }
        public bool AffectsParentArrange { get; set; }
        public bool AffectsParentMeasure { get; set; }
        public bool AffectsRender { get; set; }
        public bool BindsTwoWayByDefault { get; set; }
        public UpdateSourceTrigger DefaultUpdateSourceTrigger { get; set; }
        public bool Inherits { get; set; }
        public bool IsDataBindingallowed { get; }
        public bool IsNotDataBindable { get; set; }
        public bool Journal { get; set; }
        public bool OverridesInheritanceBehavior { get; set; }
        public bool SubPropertiesDoNotAffectRender { get; set; }

2.通过调用DependencyProperty.Register()静态方法注册属性

        static FrameworkElement()
        {
            FrameworkPropertyMetadata Metadata=new FrameworkPropertyMetadata(
                new Thickness(),FrameworkPropertyMetadataOptions.AffectsMeasure);

            MarginProperty =DependencyProperty.Register
                ("Margin",typeof(Thickness),typeof(FrameworkElement),Metadata,new ValidateValueCallback(FrameworkElement.IsMarginValid));
        }

4.添加属性包装器

创建依赖属性的最后一步是使用传统的.net属性包装WPF依赖属性。注意WPF属性使用GetValue和SetValue。下面是示例:

FrameworkElement:UIElement
{
	public Thickness Margin
	{
		set{ SetValue(MarginProperty,value); }
		get{return(Thickness)GetValue(MarginProperty);}
	}    
} 
注意:当创建属性包装器时,不应当包含任何额外的验证属性值的代码,引发事件的代码,等等。因为WPF的其他功能可能会忽略属性包装器的代码而直接调用SetValue和GetValue方法(比如在Xaml中直接为属性赋值)。事件的触发应当写在FrameworkPropertyMetadata.PropertyChangedCallback的回调函数中。

5.回调函数

在上一节中提到如果想在属性修改时能够自动引发事件,就应当实现FrameworkPropertyMetadata.PropertyChangedCallback。

这是因为在WPF中,当属性值发生变化时,依赖项属性不会自动引发事件,它们会触发一个受保护的名为OnPropertyChangedCallback()的方法。此方法通过WPF的Binding和触发器传递信息,并调用PropertyChangedCallback回调函数

        //
        // 摘要:
        //     用指定的认值和回调初始化 System.Windows.PropertyMetadata 类的新实例。
        //
        // 参数:
        //   defaultValue:
        //     依赖项对象的认值,通常作为某种特定类型的值提供。
        //
        //   propertyChangedCallback:
        //     对处理程序实现的引用,每当属性的有效值更改时,属性系统都将调用该处理程序实现。
        //
        //   coerceValueCallback:
        //     对处理程序实现的引用,每当属性系统对该属性调用 System.Windows.DependencyObject.CoerceValue(System.Windows.DependencyProperty)
        //     时都将调用此处理程序实现。
        //
        // 异常:
        //   System.ArgumentException:
        //     defaultValue 不能设置为值 System.Windows.DependencyProperty.UnsetValue;
        public PropertyMetadata(object defaultValue,PropertyChangedCallback propertyChangedCallback,CoerceValueCallback coerceValueCallback);
只要实现了PropertyChangedCallback 就能够实现属性值更改的自动响应。

6.属性验证

属性值非法时,对于传统的.net属性,可以在属性设置器中捕获这类问题。在WPF中,依赖属性使用SetValue()来进行设置属性,老方法已经不再适用。原因在第4小节中已经提到过,WPF属性系统可能直接调用SetValue()方法而跳过我们定义的验证代码

作为代替,WPF提供了两种方法来防止产生非法值:

1.ValidateValueCallback 此函数可以接受或者拒绝新值。通常被用来捕获违反属性约束的明显错误。在DependencyProperty.Register()方法中有一个参数提供了该回调函数

2.CoerceValueCallback 此函数可以将新值修改为更容易被接受的值。通常用来处理相同对象设置的依赖属性值相冲突的问题。这些值本身都是合法的,但是它们本身不相 容。此方法在FrameworkPropertyMetadata对象的构造函数中作为一个参数。

调用顺序:

在初始化时,调用ValidateValueCallback 。

在值被修改时,先调用ValidateValueCallback,然后调用CoerceValueCallback,当两者都正常时,再调用响应函数PropertyChangedCallback。

如果有父类存在,会先调用父类的ValidateValueCallback,再调用子类的ValidateValueCallback,但是强制回调CoerceValueCallback只有子类的被调用

7.最后的例子

为了帮助理解记忆,我写了一个小例子,代码比较少,直接贴出来了

    public class test:DependencyObject
    {

        public int MyProperty
        {
            get { return (int)GetValue(MyPropertyProperty); }
            set { SetValue(MyPropertyProperty,value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation,styling,binding,etc...
        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register("MyProperty",typeof(int),typeof(test),new UIPropertyMetadata(0,PropertyChanged,CoerceValue),ValidateValue);

        static void PropertyChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            MessageBox.Show(String.Format("PropertyChanged - 属性:{0} 新值:{1} 旧值:{2}",e.Property.Name,e.NewValue,e.OldValue));
        }

        //返回强制转换后的值
        static object CoerceValue(DependencyObject dobj,object newValue)
        {
            MessageBox.Show(String.Format("CoerceValue - {0}",newValue));
            return newValue;
        }

        static bool ValidateValue(object obj)
        {
            MessageBox.Show(String.Format("ValidateValue - {0}",obj));
            return true;
        }
    }
在主函数中实例一个对象
        public MainWindow()
        {
            InitializeComponent();

            test a = new test();
            a.MyProperty = 10;
        }
运行结果顺序如下:

ValidateValue - 0
---------------------------
ValidateValue - 0
---------------------------
ValidateValue - 10
---------------------------
CoerceValue - 10
---------------------------
PropertyChanged - 属性:MyProperty 新值:10 旧值:0

. 可以看到有两次的ValidateValue,应该是new test()时就调用了一次ValidateValue,然后当属性值发生改变时又调用了一次ValidateValue。符合6小节中提到的调用顺序。

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...