属性更改回调

  前一章的示例中,对各个参数的设置都非常容易理解。如果我们仅仅需要创建一个独立的依赖项属性,那么上面所提到的创建依赖项属性的基础知识足以满足需求。但是事情往往并非如此完美。在一个系统中,很少有属性是独立存在的,在WPF这种描述界面组成的类库中更是如此。例如一个属性的取值可能受其它众多属性的限制,或者一个属性值的更改可能导致其它依赖项属性值发生更改。

  在WPF的属性系统中,这一切关联关系的维护都是通过元数据以及创建属性时所传入的回调来完成的。在创建一个关联属性的时候,我们可以传入一个属性发生更改时调用的回调函数ValidateValueCallback,以及一个元数据PropertyMetadata。而在该元数据中,我们还可以记录两个属性值更改时将被调用的回调函数PropertyChangedCallback以及CoerceValueCallback。这三个回调函数到底是什么关系呢?让我们首先编写一个使用这三个回调函数的依赖项属性:

public static readonly DependencyProperty HintProperty =
DependencyProperty.Register("Hint", typeof(String), typeof(AutoCompleteEdit),
new FrameworkPropertyMetadata(String.Empty, HintChanged, CoerceHint),
new ValidateValueCallback(IsHintValid));

  而这些回调函数的简单实现如下:

public static bool IsHintValid(object value)
{
Console.WriteLine("IsHintValid called");
return true;
} public static void HintChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
Console.WriteLine("HintChanged called");
} public static Object CoerceHint(DependencyObject d, Object baseValue)
{
Console.WriteLine("CoerceHint called");
return baseValue;
}

  通过这三个函数在控制台中所打印的信息可以看到,如果我们在程序中对Hint属性进行赋值,那么这三个函数被依次调用的顺序为IsHintValid(),CoerceHint()以及HintChanged()。在依赖项属性中,这三个函数所对应的回调ValidateValueCallback,CoerceValueCallback以及PropertyChangedCallback都是用来做什么事情的呢?

  首先要看的就是ValidateValueCallback。该类型的函数用来检查当前为属性的赋值是否符合当前类型对于该属性的要求。其接受一个类型为object的参数,以代表将要被设置的属性值。由于一般情况下,每个依赖项都拥有一个CLR属性包装,而且该属性包装所拥有的类型与依赖项属性所需要的类型一致,因此在ValidateValueCallback中,软件开发人员可以直接将object类型的参数转换为依赖项属性所具有的实际类型。如果传入的属性值对于类型而言是一个有效值,那么该函数需要返回true,否则返回false。在返回false的情况下,属性系统将会抛出一个异常,以表明当前对依赖项属性的操作正试图将属性值设置为非法值。

  由于ValidateValueCallback函数是在DependencyProperty.Register()函数调用中被使用的,因此它需要是一个静态函数。又由于其仅仅接受一个用来表示将要被设置的属性值的参数value,因此该函数并没有办法访问被设置属性所在的类型实例,也就更无法根据该类型实例上的信息决定该属性值是否合理。也就是说,ValidateValueCallback仅仅施行的是类型验证,而不是实例上的验证。

  对于该类型回调的使用非常简单:在需要对某个类型属性的取值进行限制的时候,软件开发人员就需要在依赖项属性的注册过程中标明回调所需要使用的函数。就以Border类的BorderThickness属性为例:

public …… DependencyProperty BorderThicknessProperty = DependencyProperty
.Register(…, new ValidateValueCallback(Border.IsThicknessValid));

  而在IsThicknessValid()函数中,Border类则通过Thickness.IsValid()函数为BorderThickness属性的值添加了取值条件:

private static bool IsThicknessValid(object value)
{
Thickness thickness = (Thickness) value;
// 厚度值不允许为负数,不能为NaN,不能是正无穷,也不能是负无穷
return thickness.IsValid(false, false, false, false);
}

  接下来被调用的则是CoerceValueCallback。该函数用来在一个依赖项属性发生变化的时候根据其它依赖项属性限制当前依赖项属性的值。该类型的函数接受两个参数:表示需要设置属性的类型实例以及需要设置的属性值。因此在该函数中,软件开发人员可以通过类型实例来验证是否需要设置的属性值满足该实例的当前状态。如果需设置的属性值并不满足实例的当前状态,那么软件开发人员可以令其返回一个符合当前实例的受限制的值,并最终作为依赖项属性的值。需要注意的是,该函数只有在值发生变化的时候才会被调用。如果对该依赖项属性的赋值是对它的首次赋值,那么它也不会被调用。

  CoerceValueCallback所允许的一个特殊的返回值是UnsetValue。在该值被当作CoerceValueCallback的返回值时,对依赖项属性的设置变为无效。此时该依赖项属性所记录的值将仍然是该依赖项属性的原有值。

  使用CoerceValueCallback回调的一种最常见的情况就是某些属性之间所拥有的相互关联性。就以RangeBase类的Max、Min以及Value三个依赖项属性的实现为例。在依赖项属性Value的注册过程中,WPF标明了对CoerceValueCallback回调的使用:

public …… DependencyProperty ValueProperty = DependencyProperty
.Register(……new PropertyChangedCallback(RangeBase.OnValueChanged), ……);

  函数RangeBase.ConstrainToRange()则会根据当前实例上所设置的Max和Min属性验证当前所希望设置的属性值。如果Value的值大于Max的值,那么该函数将会返回最大值,以防止Value属性的值大于Max;如果Value的值小于Min的值,那么该函数将会返回最小值,以防止Value属性的值小于Min。该函数的实现代码如下:

internal static object ConstrainToRange(DependencyObject d, object value)
{
RangeBase base2 = (RangeBase) d;
double minimum = base2.Minimum;
double num2 = (double) value;
if (num2 < minimum)
{
return minimum;
}
double maximum = base2.Maximum;
if (num2 > maximum)
{
return maximum;
}
return value;
}

  我相信您现在有一个疑问:为什么微软的实现将对一个依赖项属性的验证分为了ValidateValueCallback以及CoerceValueCallback两种,即按照类型验证以及按照实例验证这两个步骤?该问题的答案需要从几个方面来说明。

  首先是WPF属性系统对于desired value的支持。在软件开发人员对一个属性的设置通过了ValidateValueCallback后,WPF属性系统就会将该值记录为该属性的desired value。但是在使用CoerceValueCallback回调进行检验的过程中,属性所表现出的数值可能由于不满足各个相关属性的约束而改变。此时该WPF属性所表现出的属性值则是该改变后的数值。在这些作为约束的相关属性发生变化的时候,CoerceValueCallback回调会被再次调用,以重新对该desired value进行约束。约束的结果可能是一个新的属性值。

  举例来说,假设一个类型中定义了Min,Max属性,以及取值应处于这两个数值之间的的Value属性。在某次对Value的赋值过程中,由于其大于Max属性所记录的数值,因此其值将被强制约束为Max属性所记录的最大值。接下来在Max属性发生变化的时候,Max属性的回调函数将通过CoerceValueCallback刷新Value的属性值。此时对Value属性的原有赋值可能已经处于Max和Min之间,从而使其回归到它的真实取值。

  在WPF系统内部,desired value功能的支持是通过ModifiedValue结构完成的:

private System.Windows.ModifiedValue EnsureModifiedValue()
{
……
System.Windows.ModifiedValue value2 = this._value as
System.Windows.ModifiedValue;
if (value2 == null)
{
value2 = new System.Windows.ModifiedValue {
BaseValue = this._value // BaseValue用来记录原始赋值
};
this._value = value2;
}
return value2;
}

  从上面的代码中可以看到,WPF内部使用一种叫ModifiedValue的类型作为其数值记录结构。该结构可以通过BaseValue属性记录对属性的基础赋值。而同时它还可以存储一些其它的信息,如由动画处理后的数值等:

internal class ModifiedValue
{
private object _animatedValue;
private object _baseValue;
private object _coercedValue;
private object _expressionValue;
……
}

  我相信到了这里您就会明白WPF属性系统是如何对某些功能进行支持的了。例如WPF是如何支持在一个属性上设置的动画并不会更改依赖项属性的原有值这一功能的。

  在前面的讲解中我们已经介绍过,一个依赖项属性的值是通过类型实例中所包含的EffectiveValueEntry类型实例记录的。而在EffectiveValueEntry类内部的众多成员函数的实现中,WPF则常常通过EnsureModifiedValue()函数得到ModifiedValue类型实例,然后对这些成员属性进行操作:

internal void SetCoercedValue(object value, object baseValue,
bool skipBaseValueChecks)
{
this.EnsureModifiedValue().CoercedValue = value;
this.IsCoerced = true;
this.IsDeferredReference = false;
}

  例如在上面代码中,SetCoercedValue()函数就首先得到了EffectiveValueEntry内所记录的ModifiedValue类型实例,并设置了它的CoercedValue属性以及相关的标志位属性。

  另外一个不得不提的知识点就是CoerceValueCallback函数对DependencyProperty的静态属性UnsetValue的使用。在WPF属性系统中,如果一个功能返回该值,那就是在提示WPF属性系统应该忽略此次功能的执行。例如在绑定的执行过程中,如果绑定的执行结果为UnsetValue,那么该次绑定的执行将被忽略。在CoerceValueCallback函数的执行过程中也是如此:如果CoerceValueCallback函数的执行结果为UnsetValue,那么该次对依赖项属性的赋值将是无效的。

  最后则是属性的更改回调PropertyChangedCallback。在该类型的函数中,软件开发人员可以通过调用CoerceValue()函数刷新其它属性,以更新与当前被赋值属性相关联的各个属性。最明显的一个例子就是Maximum属性的实现:

public …… DependencyProperty MaximumProperty = DependencyProperty
.Register(……new PropertyChangedCallback(RangeBase.OnMaximumChanged), ……); private static void OnMaximumChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
RangeBase element = (RangeBase) d;
……
element.CoerceValue(ValueProperty);
}

  好,今天就到这里。下一篇文章中,我们将深入研究属性系统中的元数据。

  转载请注明原文地址:http://www.cnblogs.com/loveis715/p/4343342.html

  商业转载请事先与我联系:silverfox715@sina.com,我只会要求添加作者名称以及博客首页链接。

WPF - 属性系统 (2 of 4)的更多相关文章

  1. WPF - 属性系统 (4 of 4)

    依赖项属性的重写 在基于C#的编程中,对属性的重写常常是一种行之有效的解决方案:在基类所提供的属性访问符实现不能满足当前要求的时候,我们就需要重新定义属性的访问符. 但对于依赖项属性而言,属性执行逻辑 ...

  2. WPF - 属性系统 (3 of 4)

    依赖项属性元数据 在前面的章节中,我们已经介绍了WPF依赖项属性元数据中的两个组成:CoerceValueCallback回调以及PropertyChangedCallback.而在本节中,我们将对其 ...

  3. WPF - 属性系统 (1 of 4)

    本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的 ...

  4. WPF - 属性系统 - APaas(AttachedProperty as a service)

    是的,文章的题目看起来很牛,我承认. 附加属性是WPF中的一个非常重要的功能.例如在设置布局的过程中,软件开发人员就常常通过DockPanel的Dock附加属性来设置其各个子元素所处的布局位置.同样地 ...

  5. WPF 属性系统 依赖属性之内存占用分析

    关于WPF的属性系统园子内有不少这方面的文章.里面大都提到了WPF依赖属性的在内存方面的优化.但是里面大都一笔带过.那么WPF到底是怎么样节约内存的.我们通过WPF属性和普通的CLR属性对比来看一下W ...

  6. wpf控件开发基础(3) -属性系统(2)

    原文:wpf控件开发基础(3) -属性系统(2) 上篇说明了属性存在的一系列问题. 属性默认值,可以保证属性的有效性. 属性验证有效性,可以对输入的属性进行校验 属性强制回调, 即不管属性有无发生变化 ...

  7. wpf控件开发基础(4) -属性系统(3)

    原文:wpf控件开发基础(4) -属性系统(3) 知识回顾 接上篇,上篇我们真正接触到了依赖属性的用法,以及依赖属性的属性元数据的用法,并且也实实在在地解决了之前第二篇提到的一系列问题.来回顾一下 属 ...

  8. wpf控件开发基础(2) -属性系统(1)

    原文:wpf控件开发基础(2) -属性系统(1) 距离上篇写的时间有1年多了.wpf太大,写的东西实在太多,我将依然围绕着自定义控件来展开与其相关的技术点. 也欢迎大家参与讨论.这篇我们将要讨论的是W ...

  9. WPF布局系统[转]

    转自:http://www.cnblogs.com/niyw/archive/2010/10/31/1863908.html前言 前段时间忙了一阵子Google Earth,这周又忙了一阵子架构师论文 ...

随机推荐

  1. setAttribute()

    ●节点分为不同的类型:元素节点.属性节点和文本节点等.   ●getElementById()方法将返回一个对象,该对象对应着文档里的一个特定的元素节点.   ●getElementsByTagNam ...

  2. C语言 · 时间转换

    问题描述 给定一个以秒为单位的时间t,要求用"<H>:<M>:<S>"的格式来表示这个时间.<H>表示时间,<M>表示分 ...

  3. DDD 领域驱动设计-商品建模之路

    最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念: 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独 ...

  4. CSS知识总结(八)

    CSS常用样式 8.变形样式 改变元素的大小,透明,旋转角度,扭曲度等. transform : none | <transform-function> <transform-fun ...

  5. gulp初学

    原文地址:gulp初学 至于gulp与grunt的区别,用过的人都略知一二,总的来说就是2点: 1.gulp的gulpfile.js  配置简单而且更容易阅读和维护.之所以如此,是因为它们的工作方式不 ...

  6. mount报错: you must specify the filesystem type

    在linux mount /dev/vdb 到 /home 分区时报错: # mount /dev/vdb /homemount: you must specify the filesystem ty ...

  7. 每天一个设计模式-7 生成器模式(Builder)

    每天一个设计模式-7 生成器模式(Builder) 一.实际问题 在讨论工厂方法模式的时候,提到了一个导出数据的应用框架,但是并没有涉及到导出数据的具体实现,这次通过生成器模式来简单实现导出成文本,X ...

  8. Maven搭建SpringMVC+Hibernate项目详解 【转】

    前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...

  9. 【JQ基础】DOM操作

    内部插入:append() //向每个匹配的元素内部追加内容,可包含 HTML 标签 $(selector).append(function(index,html)) /*•index - 可选.接收 ...

  10. ES6之let命令详解

    let与块级作用域 { var foo='foo'; let bar='bar'; } console.log(foo,'var'); //foo varconsole.log(bar ,'bar') ...