本章学习依赖项属性,英文原文 Dependency Property,它是传统 .Net Framework 属性的扩展,是 WPF 的专属,但所幸使用起来和传统属性几乎一样。WPF 元素所提供的大多数属性都是依赖项属性。
1. 定义依赖项:只能为依赖对象添加依赖项属性,即 DependencyObject,WPF 中大部分元素都继承 DependencyObject 。属性信息应该始终可用,并可能在多个对象间共享访问,因此必须将 Dependency Property 对象设置为与之关联的类的静态字段?我也没太明白,接着往下看解释:
例如,FrameworkElement 类定义了 Margin 属性,所有(继承它的)元素都共享该属性,这意味着在 FrameworkElement 类中应该这样定义 Margin:
public class FrameworkElement:UIElement,... { public static readonly DependencyProperty MarginProperty; ... }
根据约定,依赖项属性的名称在末尾加上“Property”以识别和普通属性的不同。
2. 注册依赖项:使用前先注册,这一步需要确保在任何使用属性的代码前完成,因此必须在与之关联的静态构造函数中完成(readonly 关键字定义了该字段只能在其静态构造中被修改)。WPF 确保 DP 对象不能被直接实例化, 因为 DP 类没有公共构造函数,反而只能使用 DependencyProperty.Register() 方法创建,WPF 还确保其创建之后不能被修改,因为属性的值要作为 Register 方法的参数在静态构造中被设置。
先创建一个 FrameworkPropertyMetadata(该值表示希望通过依赖项属性使用什么服务,如支持数据绑定、动画或日志),然后,再用 DependencyProperty.Register() 注册
3. 像原来一样使用依赖项属性:注册后,使用传统的 .Net 包装一下,WPF 是使用 DependencyObject 基类中定义的 SetValue 和 GetValue 方法,如下:
public Thickness Margin{ set{ SetValue(MarginProperty,value); } get{ return (Thickness)GetValue(MarginProperty); } }
提示,在创建属性包装器是,应该只包含对 SetValue 和 GetValue 两个方法的操作,不要额外假如数据验证、事件操作等,因为这有可能会导致这些元素忽略正常的 Set 和 Get。清除属性值有特定的方法,继承自 DependencyObject 的 ClearValue 方法:myElement.ClearValue(FrameworkElement.MarginProperty)
4. 检索属性值:因为依赖项属性依赖于多个属性提供者,每个提供者都有自己的优先级,所以 WPF 以一种逐级检索的方式确定使用哪个值,其层级顺序(从低到高):
(1) 默认值,由 FrameworkPropertyMetadata 设置的值
(2) 通过继承,如果设置了 FrameworkPropertyMetadata.Inherits 标志,并且为包含层次中的某一个元素提供一个值(说实话,这句话没明白)
(3) 来自主题样式的值
(4) 来自项目样式的值
(5) 本地值,即使用代码或XAML直接设置的值
如上所示,可以通过直接应用一个值而覆盖掉整个层次,否则将由列表中前一个可用项确定。
5. 共享依赖项属性:一些类会共享同一个依赖项属性,比如 TextBlock 和 Control 都有 FontFamily 属性,他们指向同一个静态的依赖项属性,该属性实际上是在 TextElement 类中定义的 TextElement.FontFamilyProperty,由 TextElement 的静态构造函数注册,那两个类通过在其静态构造函数中嗲用 DependencyProperty.AddOwner() 方法重用该属性:
TextBlock.FontFamily = TextElement.FontFamilyProperty.AddOwner( typeof( TextBlock ) )
6. 属性验证:不要把验证属性值的代码像原来一样放到设置属性的逻辑中去,因为可能通过 WPF 的属性设置系统直接使用 SetValue 方法设置,从而使验证部分失灵(被绕过了),WPF 提供两种回调方法实现:
(1) ValidateValueCallback 可以接受或拒绝新值,通用用于捕捉违背属性约束的明显错误,可作为 DependencyProperty.Register() 的一个参数提供
(2) CoerceValueCallback 将新值修改为更能被接受的值,通常用于处理本身合法但与原值或某些层级默认值相冲突的新值,该方法作为构造 FrameworkPropertyMetadata 的一个参数提供
当应用程序设置一个依赖项属性时,它们的作用过程是:
(1) CoerceValueCallback 先有机会修改提供的值,或返回 DependencyProperty.UnserValue ,这会完全拒绝修改
(2) 激活 ValidateValueCallback,该方法返回 true 表示接受修改,返回 false 表示拒绝值。方法不能访问为之设置属性的实际对象,意味着,不能检查其它属性值。
(3) 以上两个方法都成功了,触发 PropertyChangedCallback 方法,这是如果希望通知其它类,可以在此触发事件
7. 验证回调:依赖项属性在注册时,Register 方法其中有一个可选参数,就是设置验证回调方法,写法如下
MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness),typeof(FrameworkElement),metadata, new ValidateValueCallback(FrameworkElement.IsMarginValid))
最后一句就是验证回调,回调方法必须指向一个接受 object 参数并返回 bool 的方法,也就是说语句中的 IsMarginValid 方法必须是
private static bool IsMarginValid(object value){ }
对于验证回调,有一个限制,就是必须是静态方法且无权访问正在被验证的对象。所能够获得的信息就只是刚被传进来的 value,比如某元素有 Maximum 和 Minimum 两个属性,设置 Maximum 时候应判断不能小于 Minimum,但这在验证回调函数中是不能实现的,因为它不能访问 Minimum 。解决这种情况,可以使用 6 中提到的 CoerceValueCallback,即强制回调。
8. 强制回调:通过 FrameworkPropertyCallback 使用 CoerceValueCallback,如
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.CoerceValueCallback = new CoerceValueCallback( CoerceMaximum ); DependencyProperty.Register( "Maximum",typeof(double),typeof(Rangebase),metadata);
通过 CoerceValueCallback 可以处理互相关联的属性。对于强制回调的应用,在书中有一大段实例说明,忘记的话还是回去翻翻书吧。