依赖属性支持的特征包括:动画、数据绑定、样式。由WPF元素暴露的属性大部分都是依赖属性。依赖属性和常规属性的使用方法相同。
WPF设计了依赖属性支持其特有的动态特性,并且不干扰其他系统的.net代码。
创造一个依赖属性的语法完全不同于创造一个普通的.NET属性。
第一步是定义一个代表属性的对象。这是DependencyProperty类的一个实例。关于你属性的信息需要一直是可用的,甚至可能在类之间共享(常见在WPF元素中)。因此,你的DependencyProperty对象必须被定义为相关类的一个静态字段。
例如,FrameworkElement类定义了一个Margin属性。Margin被所有元素共享,是一个依赖属性:
public class FrameworkElement: UIElement, ... { public static readonly DependencyProperty MarginProperty; ... }
根据命名约定,定义依赖属性的字段名字是正常属性加上单词Property后缀。那样,你能区分依赖属性定义和实际属性的名字。字段使用readonly关键字,这意味着它只能在FrameworkElement类的静态构造函数中设置,如何设置见下节。
下一步是注册你的依赖属性。因为要在使用属性之前完成注册,必须在相关类的一个静态构造函数中执行它。
不能直接实例化DependencyProperty对象,因为DependencyProperty类没有公开的构造函数。代替,一个DependencyObject实例只能使用静态的DependencyProperty.Register()方法被创造。DependencyProperty对象被创造之后不能再修改,因为所有的DependencyProperty成员是只读的。代替,它们的值必须作为Register()方法的参数被提供。
下面例子显示FrameworkElement类的Margin属性是如何被注册的:
static FrameworkElement() { var metadata = new FrameworkPropertyMetadata( new Thickness(), FrameworkPropertyMetadataOptions.AffectsMeasure); MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement), metadata, new ValidateValueCallback(FrameworkElement.IsMarginValid)); ... }
注册一个依赖属性包括二步。首先,你创造一个FrameworkPropertyMetadata对象,指明希望使用依赖属性的什么服务(诸如支持数据绑定,动画,和日志)。其次,依靠调用DependencyProperty.Register()静态方法注册属性。这时,你负责提供一些关键成分:
两个可选属性值得研究。FrameworkPropertyMetadata的详细描述见95页。
创造依赖属性的最后一步是用一个传统.NET属性包装它。WPF属性使用的是定义在DependencyObject基类的GetValue()和SetValue()方法。
public Thickness Margin { set { SetValue(MarginProperty, value); } get { return (Thickness)GetValue(MarginProperty); } }
当你创造属性包装时,你应该仅调用SetValue()和GetValue(),如在前例中。你不应该添加任何额外的代码验证值,引起事件,等等。那是因为另外的特征可能旁路属性包装,直接调用SetValue()和GetValue()。(一个例子是在运行时当一个编译XAML文件被解析时。)SetValue()和GetValue()都是公开的。
验证输入值应使用DependencyProperty.ValidateValueCallback。
引发事件应使用FrameworkPropertyMetadata.PropertyChangedCallback 。
现在可以使用属性了:
myElement.Margin = new Thickness(5);
之后,可能希望移除局部值设置,仿佛你从未设置它。依靠从DependencyObject继承的ClearValue()方法。
myElement.ClearValue(FrameworkElement.MarginProperty);
依赖属性支持两个关键的行为:改变通知和动态值求解。
如果你希望对一个属性的改变作出反应,你有二个选择—创造一个绑定,或写一个触发器。但是,依赖属性没有提供响应属性值改变的事件。
依赖属性因动态值求解的行为而得名。一个依赖属性依赖于多个属性提供者,每个提供者带有它自己的优先级。当你从一个属性值取回一个值时,WPF属性系统经历一系列步骤求得最终值。首先,它通过考虑下列因素决定属性的基值。优先级从最低的到最高排列(最下面的赢):
基值不一定是最终的属性值,WPF通过4个步骤求得属性值:
一些类共享依赖属性,即使他们有独立的类层次结构。例如,TextBlock.FontFamily和Control.FontFamily都指向同一个依赖属性,它实际上被定义在TextElement类中以及TextElement.FontFamilyProperty。TextElement类的静态构造函数注册属性,但是TextBlock和Control类的静态构造函数简单地调用DependencyProperty.AddOwner()方法重用它:
TextBlock.FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));
自定义类
副作用
附加属性是依赖属性,并且它被WPF属性系统管理。区别是应用附加属性的类不是定义它的类。
为定义一个附加属性,你使用RegisterAttached()方法而不是Register()。这是一个注册Grid.Row属性的例子:
var metadata = new FrameworkPropertyMetadata( 0, new PropertyChangedCallback(Grid.OnCellAttachedPropertyChanged)); Grid.RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int), typeof(Grid), metadata, new ValidateValueCallback(Grid.IsIntValueNotNegative));
就像一个普通的依赖属性,你能提供一个FrameworkPropertyMetadata对象和一个ValidateValueCallback。
当创造附加属性时,你没有定义.NET属性包装。那是因为附加属性能被设置在任何依赖对象上。例如,Grid.Row属性可能被设置在一个Grid对象上(如果你有一内部嵌套另一个Grid的Grid)或在另外的一些元素上。事实上,即使元素不在一个Grid内,和即使在你的元素树上不存在Grid对象,Grid.Row属性也能被设置在那个元素上。
代替使用一个.NET属性包装,附加属性要求能调用的一对静态方法去设置和获得属性值。这些方法使用熟悉的SetValue()和GetValue()方法(从DependencyObject类继承)。静态的方法应该被命名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); }
使用代码设置一个元素位于网格的第一行:
Grid.SetRow(txtElement, 0);
可选地,你能直接调用SetValue()或GetValue()方法,旁路掉静态方法:
txtElement.SetValue(Grid.RowProperty, 0);
WPF提供两种方法阻止无效值:
这里是当应用程序试图设置一个依赖属性时:所有的部件如何起作用:
相当于正常属性的set部分中的验证。
其签名为接受一个object输入参数,返回布尔值。返回值为真表示接受,为假表示拒绝。
private static bool IsMarginValid(object value) { Thickness thickness1 = (Thickness) value; return thickness1.IsValid(true, false, true, false); }
有一个限制,它是一个静态方法,不能访问正被验证的对象,不能使用对象中的其他属性。
用于验证互相关联的属性
private static object CoerceMaximum(DependencyObject d, object value) { RangeBase base1 = (RangeBase)d; if (((double) value) < base1.Minimum) { return base1.Minimum; } return value; }