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小节中提到的调用顺序。