三,依赖项属性
- 定义依赖项属性
public class FrameworkElement : UIElement { public static readonly DependencyProperty MarginProperty; static FrameworkElement() { FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.None); MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement), metadata, null); } public Thickness Margin { set { SetValue(MarginProperty, value); } get { return (Thickness)GetValue(MarginProperty); } } }
只能为依赖对象(继承自DependencyObject的类)添加依赖项属性。
它是DependencyProperty类的实例,属性信息应该始终保持可用,甚至可能需要在多个类中共享这些信息,因此,必须将DependencyProperty对象定义为与之相关联的类的静态字段。
根据约定,定义依赖项属性的字段的名称为在普通属性的末尾加上单词“Property”,且使用readonly关键字进行修饰。
DependencyProperty不能被直接实例化,因为DependencyProperty没有公有的构造函数,只能使用静态的DependencyProperty.Register()方法创建。
注册一个依赖项属性需要经过两个步骤,首先创建一个FrameworkPropertyMetadata对象,该对象希望通过依赖项属性使用什么服务。其次通过调用DependencyProperty.Register()静态方法注册属性,在这一步中需提供以下几个要素:
1) 属性名:在该示例中为Margin
2) 属性使用的数据类型:在该示例中为Thickness结构
3) 拥有该属性的类型:在该示例中为FrameworkElement类
4) 一个具有附加属性设置的 FrameworkPropertyMetadata对象,该要素是可选的
5) 一个用于验证属性的回调函数,该要素是可选的
FrameworkPropertyMetadata类的所有属性的说明如下:
删除依赖项属性的本地值设置:myElement.ClearValue(FrameworkElement.MarginProperty); -
WPF使用依赖项属性的方式
依赖项属性依赖于多个属性提供者,每个提供者都有其自己的优先级,当从属性检索值时,WPF属性系统会通过一系列步骤获取最终的值,它首先通过以下因素来决定基本值,优先级从低到高:
1) 默认值(由FrameworkPropertyMetadata对象设置的值)。
2) 继承而来的值。
3) 来自主题样式的值。
4) 来自项目样式的值。
5) 本地值(即使用代码或直接用XAML直接为对象设置的值)
WPF使用上面的列表确定依赖项属性的基本值,但是基本值未必就是最后从属性中检索到的值,这是因为WPF还需要考虑其他几个可能改变属性值的提供者,下面是WPF决定一个属性值的四步骤过程:
1) 确定基本值。
2) 如果属性是用表达式设置的,则对表达式进行求值。
3) 如果该属性是动画的目标,应用该动画。
4) 运行 CoerceValueCallback回调函数修正属性值。 -
共享的依赖项属性
一些类会共享同一个依赖项属性,尽管这些类具有不同的继承层次,例如,TextBlock.FontFamily属性和Control.FontFamily属性都指向TextElement类中定义的TextElement.FontFamilyProperty依赖项属性,TextElement类的静态构造函数注册该属性,而TextBlock类和Control类的静态构造函数只是简单的通过调用DependencyProperty.AddOwner()方法重用该属性:TextBlock.FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));
可以使用重载的AddOwner()方法提供一个验证回调函数以及一个仅应用于依赖项属性新用法的新 FrameworkPropertyMetadata对象。
在WPF中重用依赖项属性可以得到一些奇异的效果,最有名的是样式。例如,如果使用样式自动设置TextBlock.FontFamily属性,样式也会影响Control.FontFamily属性,因为在后台这两个类使用同一个依赖项属性。 -
附加属性
附加属性也是依赖属性,它和依赖属性的区别是注册附加属性时使用DependencyProperty.RegisterAttached()方法,而不是DependencyProperty.Register()方法。例如:FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(0, new PropertyChangedCallback(null)); Grid.RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int), typeof(Grid), metadata, new ValidateValueCallback(null));
当创建附加属性时不需要定义.Net属性包装器,这是因为附加属性可以被用于任何依赖对象。但它需要调用命名为SetPropertyName()和GetPropertyName()的两个静态方法来设置和获取属性值,如以下是实现Grid.Row附加属性的静态方法:
public static int GetRow(UIElement element) { if (element == null) { throw new ArgumentNullException(); } return (int)element.GetValue(Grid.RowProperty); } public static void SetRow(UIElement element, int value) { if (element == null) { throw new ArgumentNullException(); } element.SetValue(Grid.RowProperty, value); }
- 属性验证
当定义任何类型的属性时,都需要面对错误设置属性的可能性,对于传统的.Net属性,可以尝试在属性设置器中捕获这类问题。但对于依赖属性,这种方法不合适,因为可以通过WPF属性系统使
用SetValue()方法直接设置属性。作为替代,WPF提供了两种方法用于阻止非法值:
ValivateValueCallback:调用该函数可以接受或拒绝新值,通常,它用于捕获违反属性约束的明显错误,可以作为DependencyProperty.Register()方法的一个参数提供该回调函数。
CoerceValueCallback:该回调函数可将新值修改为更能被接受的值。 - 验证回调
DependencyProperty.Register()方法接受一个可选的验证回调:
MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement), metadata, new ValidateValueCallback(FrameworkElement.IsMarginValid)); private static bool IsMarginValid(object value) { Thickness thickness = (Thickness)value; return thickness.IsValid(); }
对于验证回调函数有一个限制:它们必须是静态方法而且无权访问正在被验证的对象,所有能够获得和信息只有传入的参数。
- 强制回调
通过FrameworkPropertyMetadata对象使用CoerceValueCallback回调,它可以处理相互关联的属性。例如,ScrollBar控件提供了Maximum、Minimum和Value属性,这些属性都继承自RangeBase类,保持对这些属性进行调整的一种方法是使用属性强制。例如,当设置Maximum属性时,必需进行强制,以确保不能小于为Minimum属性的值:
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.CoerceValueCallback = new CoerceValueCallback(CoerceMaximum); DependencyProperty.Register("Maximum", typeof(double), typeof(RangeBase), metadata); private static object CoerceMaximum(DependencyObject d, object value) { RangeBase base1 = (RangeBase)d; if (((double)value) < base1.Minimum) { return base1.Minimum; } return value; }