WPF 依赖属性 DependencyProperty

来源:互联网 发布:centos git使用教程 编辑:程序博客网 时间:2024/04/29 04:24

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:        //     对回调的引用,除了典型的类型验证之外,该引用还应执行依赖项对象值的任何自定义验证。(可选)        //        // 返回结果:        //     一个依赖项对象标识符,应使用它在您的类中设置 public static readonly 字段的值。        //然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。        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小节中提到的调用顺序。