依赖项属性
依赖项属性是标准.NET属性的全新实现–具有大量新增价值。在 WPF的核心特性(如动画、数据绑定以及样式)中需要嵌入依赖项属性。WPF 元素提供的大多数属性都是依赖项属性。依赖项属性的用法和普通属性是相同的。
只能为依赖对象(继承自DependencyObject
的类)添加依赖属性。幸运的是,WPF基础结构的关键部分中的大部分都简介继承自DependencyObject
类,最明显的例子就是元素
定义依赖项属性
第一步是定义表示属性的对象,它是DependencyProperty
类的实例。属性信息应该始终保持可用,甚至可能需要在多个类之间共享这些信息。因此,必须将DependencyProperty
对象定义为与其相关联的类的静态字段。
1 | public class FrameworkElement : UIElement |
根据约定,定义依赖属性项属性的字段的每次是在普通属性的末尾处加上单词“Property
”。根据这种命名方式,可从实际属性的名称中区分出依赖项属性的定义。字段的定义使用了readonly关键字,这意味着只能在FrameworkElement
类的静态构造函数中对其进行设置
注册依赖项属性
1 | static FrameworkElement() |
注册依赖项属性需要经历两个步骤。首先创建FrameworkPropertyMetadata
对象,该对象指示希望通过依赖项属性使用什么服务(如支持数据绑定/动画以及日志)。接下来通过调用DependencyProperty.Register()
静态方法注册属性。在这一步骤中,需要提供以下几个要素:
- name: 属性名
- propertyType: 属性使用的数据类型
- ownerType: 拥有该属性的类型
- typeMetadata: 一个具有附加属性设置的
FrameworkPropertyMetadata
对象,该要素是可选的 - validateValueCallback: 一个用于验证属性的回调函数,该要素是可选的
名称 | 说明 |
---|---|
AffectsArrange、AffectsMeasure、AffectsParentArrange和AffectsParentMeasure | 如果为true,依赖项属性会影响在布局操作的测量过程和排列过程中如何放置相邻的元素或父元素。 |
AffectsRender | 如果为true,依赖项属性会对属性的绘制方式造成一定的影响,要求重新绘制元素 |
BindsTwoWayByDefault | 如果为true,默认情况下,依赖项属性将使用双向数据绑定而不是单向数据绑定,不过,但创建数据绑定时,可以明确指定所需的绑定行为 |
Inherits | 如果为true,就通过元素树传播该依赖项属性值,并且可以被嵌套的元素继承 |
IsAnimationProhibited | 如果为true,就不能将依赖项属性用于动画 |
IsNotDataBindable | 如果为true,就不能使用绑定表达式设置依赖项属性 |
Journal | 如果为true,在基于页面的应用程序中,依赖项属性将被保存到日志(浏览过的页面的历史纪录)中 |
SubPropertiesDoNotAffectRender | 如果为true,并且对象的某个指数型(属性的属性)发生了变化,WPF将不会重新渲染该对象 |
DefaultUpdateSourceTrigger |
但该属性用于绑定表达式时,该属性用于为Binding.UpdateSourceTrigger属性设置默认值。UpdateSourceTrigger属性决定了 数据绑定值在何时应用自身的变化。但创建绑定时,可以手动设置UpdateSourceTrigger属性 |
DefaultValue |
该属性用于为依赖项属性设置默认值 |
CoerceValueCallback | 该属性提供了一个回调函数,用于在验证依赖项属性之前尝试“纠正”属性值 |
PropertyChangedCallback |
该属性提供了一个回调函数,当依赖项属性的值发生变化时调用该回调函数 |
添加属性包装器
创建依赖项属性的最后一个步骤是使用传统的.NET属性封装 WPF 依赖项属性。但典型的属性过程是检索或设置某个私有字段的值,而 WPF 属性的属性过程是使用在 DependencyObiec基类中定义的 GetValue()和 SetValue()方法,删除值使用ClearValue()方法
1 | public Thickness Margin |
属性封装器不是验证数据或引发事件的正确位置。不过WPF的确提供了用于进行这些工作的地方——技巧是使用依赖项属性回调函数DependencyProperty.ValidateValueCallback
回调函数进行验证操作;FrameworkPropertyMetadata.PropertyChangedCallback
回调函数进行监听操作
WPF使用依赖项属性的方式
从依赖项属性检索值时,WPF需要考虑多个方面。依赖项属性因该行为得名–本质上,依赖项属性依赖于多个属性提供者,每个提供者都有各自的优先级。当从属性检索值时,WPF属性系统会通过一系列步骤获取最终值。首先通过考虑以下因素(按优先级从低到高的顺序排列)来决定基本值(base value):
- 默认值(由FeameworkPropertyMetadata对象设置的值)
- 继承而来(假设设置FrameworkPropertyMetadata.Inherits标志,并为包含层次中的某个元素提供了值)
- 来自主题样式的值
- 来自项目样式的值
- 本地值(使用代码或XAML直接为对象设置的值)
共享的依赖项属性
尽管一些具有不同的继承层次,但它们会共享同一依赖属性,例如TextBlock.FontFamily属性和Control.FontFamily属性指向同一个静态的依赖项属性,该属性实际上是在 TextElemen类中定义的 TextElement.FontFamilyProperty 依赖项属性。TextElement 类的静态构造函数注册该属性,而TextBlock类和 Control类的静态构造函数只是通过调用DependencyProperty.AddOwner()方法重用该属性:
1 | TextBlock.FontFamilyProperty = |
附加的依赖项属性
附加属性时一种依赖项属性,由WPF属性系统管理。不同之处在于附加属性被引用到的类并非定义附加属性的那个类。例如,Grid类定义了Row和Column附加属性,这两个属性被用于设置Gird面板包含的元素,以指明这些元素应被放到哪个单元格中
1 | FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata( |
当创建附加属性时,不必定义.NET 属性封装器。这是因为附加属性可以被用于任何依赖对象。例如,Grid.Row 属性可能被用于 Grid 对象(如果在 Grid 控件中嵌套了另一个 Grid 控件),也可能被用于其他元素。实际上,Grid.Row属性甚至可以被用于并不位于 Grid 控件中的元素甚至在元素树中根本就不存在 Grid 对象。
不是使用.NET属性封装器,反而附加属性需要调用两个静态方法来设置和获取属性值,这两个方法使用为人熟知的 SetValue()和 GetValue()方法(继承自 DependencyObiect 类)。这两个静态方法应当命名为SetPropertyName()和GetPropertyName()
1 | public static int GetRow(UIElement element) |
属性验证
在定义任何类型的属性时,都需要面对错误设置属性的可能性。对于传统的.NET属性,可尝试在属性设置器中捕获这类问题。但对于依赖项属性而言,这种方法不合适,因为可能通过WPF属性系统使用SetValue()方法直接设置属性。
作为代替,WPF 提供了两种方法来阻止非法值:
- ValidateVaalueCallback: 该回调函数可接受或拒绝新值。通常,该回调函数用于驳货违反了属性约束的明显错误。可作为DependencyProperty.Register()方法的一个参数提供该回调函数
- CoerceValueCallback: 该回调函数可将新值修改为更能被接受的值。该回调函数通常用于处理为相同对象设置的依赖项属性值相互冲突的问题。这些值本身可能时合法的,但当同时应用时它们时不相容的。为了使用这个回调函数,但创建FrameworkPropetyMetadata对象时(然后对该对象将被传递到DependencyProperty.Register()方法),作为构造函数的一个参数提供回调函数
下面是当应用程序试图设置依赖项属性时,所有这些内容的作用过程:
- 首先,CoerceValueCallback方法有机会修改提供的值(通常,使提供的值和其他属性相容),或者返回 DependencyProperty.UnsetValue,这会完全拒绝修改。
- 接下来激活 ValidateValueCallback方法。该方法返回 true 以接受一个值作为合法值,或者返回 false 拒绝值。与 CoerceValueCallback 方法不同,ValidateValueCallback 方法不能访问设置属性的实际对象,这意味着您不能检查其他属性值。
- 最后,如果前两个阶段都获得成功,就会触发 PropertyChangedCallback 方法。此时如果希望为其他类提供通知,可以引发更改事件。
验证回调
1 | MarginProperty = DependencyProperty.Register( |
可使用这个回调函数加强验证,验证通常应被添加到属性过程的设置部分。提供的回调函数必须指向一个接受对象参数并返回 Boolean 值的方法。返回 true 以接受对象是合法的,返回false 拒绝对象。
强制回调
通过 FrameworkPropertyMetadata对象使用 CoerceValueCallback 回调函数。下面是示例:
1 | static RangeBase() |