属性变更回调
上一章的例子中,各个参数的设置都非常容易理解。如果我们只需要创建一个独立的依赖属性,那么上面提到的创建依赖属性的基础知识就足够了。但事情往往不是那么完美。在一个系统中,很少有属性是独立存在的,尤其是在像WPF这样描述界面组成的类库中。例如,一个属性的值可能会受到许多其他属性的限制,或者一个属性值的变化可能会导致其他依赖属性的值发生变化。
在WPF属性系统中,所有关系的维护都是通过创建属性时传入的元数据和回调来完成的。创建关联属性时,我们可以传入一个属性更改时调用的回调函数ValidateValueCallback,以及一个元数据PropertyMetadata。在这个元数据中,我们还可以记录属性值改变时会调用的两个回调函数PropertyChangedCallback和CoerceValueCallback。这三个回调函数之间有什么关系呢?让我们首先编写一个使用这三个回调函数的依赖属性:
这些回调函数的简单实现如下:
从控制台中这三个函数打印的信息可以看到,如果我们在程序中给Hint属性赋值,这三个函数的调用顺序是IsHintValid()、CoerceHint()和HintChanged ()。依赖属性中,这三个函数对应的回调Validate Callback、Coerce Callback、PropertyChangedCallback分别是做什么用的?
首先要看的是验证Value回调。该类型的函数用于检查分配给属性的当前值是否满足该属性当前类型的要求。它接受一个object类型的参数来表示要设置的属性值。由于一般情况下,每个依赖项都有一个 CLR 属性包装器,并且属性包装器的类型与依赖项属性所需的类型一致,因此在 Validate Value Callback 中,软件开发人员可以直接将对象类型参数转换为依赖属性的实际类型。如果传入的属性值是该类型的有效值,则该函数需要返回 true,否则返回 false。如果为 false,属性系统将抛出异常,表明当前对依赖属性的操作正在尝试将属性值设置为非法值。
由于 Validate Value Callback 函数用于 DependencyProperty.Register() 函数调用中,因此它需要是静态函数。并且由于它只接受一个表示要设置的属性值的参数值,因此该函数无法访问正在设置属性的类型实例,并且无法根据类型上的信息来确定该属性值是否是基于类型的信息。实例。合理的。也就是说,Validate Callback只进行类型验证,不进行实例验证。
此类回调的使用非常简单:当需要限制某类属性的值时,软件开发者需要在依赖属性的注册过程中指明回调所使用的函数。以Border类的BorderThickness属性为例:
在IsThicknessValid()函数中,Border类通过Thickness.IsValid()函数为BorderThickness属性的值添加取值条件:
下一个回调是“强制Value回调”。该函数用于当一个依赖属性发生变化时,根据其他依赖属性来限制当前依赖属性的值。该类型函数接受两个参数:需要设置属性的类型实例和需要设置的属性值。因此,在该函数中,软件开发人员可以使用类型实例来验证需要设置的属性值是否满足实例的当前状态。如果需要设置的属性值不满足实例的当前状态,软件开发人员可以让它返回一个与当前实例匹配的受限值,并最终作为依赖属性的值。需要注意的是,只有当值改变时才会调用该函数。如果对依赖属性的赋值是第一次对其进行赋值,则也不会调用它。
Coerce Callback 允许的一个特殊返回值是 Unset Value。当该值作为Coerce Value Callback的返回值时,依赖属性的设置将失效。此时,依赖属性记录的值仍将是依赖属性的原始值。
使用强制Value回调的最常见情况之一是某些属性之间的相关性。以RangeBase类的Max、Min、Value这三个依赖属性的实现为例。在注册依赖属性 Value 的过程中,WPF 指示使用 Coerce Value Callback 回调:
RangeBase.ConstrainToRange() 函数将根据当前实例上设置的 Max 和 Min 属性来验证当前所需的属性值。如果Value的值大于Max的值,那么该函数将返回最大值,以防止Value的属性值大于Max;如果Value的值小于Min的值,则该函数将返回最小值,以防止Value的属性值大于Max。该值小于最小值。该功能的实现代码如下:
相信你现在有一个疑问:为什么微软的实现会将依赖属性的验证分为两种:Validate Callback和Coerce Callback,即类型验证和实例验证两个步骤?这个问题的答案需要从几个方面来解释。
第一个是WPF属性系统对期望值的支持。软件开发人员通过 Validate 回调设置属性后,WPF 属性系统会将该值记录为该属性的所需值。然而,在使用Coerce Value Callback回调进行测试的过程中,由于不满足各个相关属性的约束,属性显示的值可能会发生变化。此时,WPF属性显示的属性值就是改变后的值。当这些作为约束的相关属性发生变化时,将再次调用 Coerce Value Callback 回调来重新约束所需的值。约束的结果可以是新的属性值。
例如,假设某个类型定义了 Min 和 Max 属性,以及其值应介于这两个值之间的 Value 属性。在对Value进行某次赋值时,由于大于Max属性记录的值,因此其值将被强行约束为Max属性记录的最大值。接下来,当Max属性发生变化时,Max属性的回调函数会通过Coerce Value Callback刷新Value的属性值。此时,分配给Value属性的原始值可能已经在Max和Min之间,从而将其恢复为真实值。
WPF系统内,通过Modified Value结构完成对期望值函数的支持:
从上面的代码可以看出,WPF内部使用了一种名为Modified Value的类型作为其数值记录结构。该结构体可以通过Base属性来记录属性的基本赋值。同时,它还可以存储一些其他信息,例如动画处理后的值:
相信到这里你就会明白WPF属性系统是如何支持某些功能的。例如,WPF 如何支持在不更改依赖属性的原始值的情况下对属性进行动画处理的能力。
正如我们在前面的解释中所介绍的,依赖属性的值是通过类型实例中包含的有效条目类型实例来记录的。在Effective Value Entry类内部很多成员函数的实现中,WPF经常通过EnsureModified Value()函数获取Modified Value类型实例,然后对这些成员属性进行操作:
例如上面的代码中,SetCoerced()函数首先获取Effective Entry中记录的Modified类型实例,并设置其Coerced属性和相关标志属性。
另外一个必须提到的知识点是Coerce Callback函数对DependencyProperty的静态属性Unset的使用。在WPF属性系统中,如果一个函数返回这个值,则提醒WPF属性系统应该忽略这个函数的执行。例如,在执行绑定过程中,如果绑定的执行结果是Unset Value,则绑定的执行将被忽略。在Coerce Callback函数的执行过程中也是如此:如果Coerce Callback函数的执行结果为Unset,那么对依赖属性的赋值将无效。
最后是属性改变回调PropertyChangedCallback。在此类函数中,软件开发人员可以通过调用 CoerceValue() 函数来刷新其他属性,以更新与当前分配的属性关联的每个属性。最明显的例子是 Maximum 属性的实现:
好啦,今天就到此为止。在下一篇文章中,我们将更深入地了解属性系统中的元数据。
转载请注明原文地址:http://www.sychzs.cn/loveis715/p/4343342.html
商业转载请提前联系我:support@www.sychzs.cn。我只会要求添加作者姓名和博客主页链接。
-->