依赖项属性的重写html
在基于C#的编程中,对属性的重写经常是一种行之有效的解决方案:在基类所提供的属性访问符实现不能知足当前要求的时候,咱们就须要从新定义属性的访问符。编程
但对于依赖项属性而言,属性执行逻辑的从新定义并不能存在于CLR属性包装中:WPF内部对依赖项属性的实现要求依赖项属性的CLR包装实现仅仅调用GetValue()以及SetValue()属性,而不能提供其它的自定义逻辑。相反地,咱们须要经过更改建立时所传入的元数据来指定自定义属性执行逻辑,甚至在某些更苛刻的要求下,如更改依赖项属性的类型,从新定义一个具备相同名称的依赖项属性。安全
对一个依赖项属性的重写很是简单。若是一个类型从其基类中继承了一个依赖项属性,那么软件开发人员能够在派生类中经过OverrideMetadata()方法完成对属性元数据的覆盖。在重写元数据的时候,系统会将新的元数据与以前的依赖项属性元数据中的各信息进行合并或替换:ide
1) 合并PropertyChangedCallback。函数
2) 替换DefaultValue。this
3) 替换CoerceValueCallback的实现。spa
4) 合并ValidationCallback。code
5) 对于FrameworkPropertyMetadata而言,FrameworkPropertyMetadataOptions的标志组合为按位或运算。htm
这里所提到的操做主要分为两种:合并和替换。在这里,合并的意思就是在类型的继承层次中的全部对该组成的赋值都将会被保留。在须要执行该组成的时候,WPF属性系统会按照类型的继承层次依次调用该组成。而替换则表示当前对该组成的声明将会彻底替换其全部基类中所声明的该组成。接下来,WPF属性系统仅仅会调用类型继承层次中最高层次的类型所声明的该组成。对象
如今咱们来看看元数据中各个组成采起合并或是替换的理由。首先要讨论的就是PropertyChangedCallback。该回调所作的事情就是在一个依赖项属性发生了更改的时候刷新其它该类型所包含的依赖项属性。固然,这种逻辑在依赖项属性声明的类型中实现是最正常的一种想法:在同一个类型中调用该函数,刷新其它依赖项属性值能够保证该类型实例处于正常的状态。
在属性发生更改的时候,系统将首先调用最高层次派生类中所设置的PropertyChangedCallback回调,并沿类型层次结构依次调用各基类实现所提供的各个回调。就像前几节中的实例代码所展现的那样,这些回调经常经过调用CoerceValue来完成其它相关联依赖项属性的更新。经过这一系列回调,该类型继承层次中的各个类型都将处于一个正常的状态。
对DefaultValue的替换则很是容易理解:因为一个属性不能同时拥有多个默认值,所以使用新的默认值替换基类中所声明的默认值是一种很是正常的选择。
接下来则是CoerceValueCallback。在依赖项属性发生变化的时候,属性系统将仅调用最直接元数据的CoerceValueCallback。这是由于基类中的CoerceValueCallback回调并不了解派生类中的各个属性,所以一旦定义了新的CoerceValueCallback回调,基类中所定义的逻辑将再也不适合对依赖项属性的值进行约束。
下一个须要讨论的组成则是ValidationCallback回调。因为该函数是在属性注册时传入的,而不是做为元数据中所储存的数据存储在属性系统中。所以它没法被新的属性注册所覆盖。同时不将其添加到元数据中的理由:万一覆盖了,那还须要将全部原ValidationCallback回调中的逻辑重写一遍。
最后一个须要说明的则是元数据选项的处理。在经过OverrideMetadata()方法操做一个元数据所记录的各个元数据选项的时候,全部的元数据选项将被合并。固然,这里有一种状况就是消除以前设置的元数据选项。在须要达到该目的的时候,咱们须要将该元数据选项所对应的属性设置为false。举例来讲,软件开发人员能够在元数据中经过NotDataBindable标记设置一个依赖项属性不能被绑定。可是若是须要经过OverrideMetadata()函数清除该选项的时候,软件开发人员就须要在传入的元数据上将IsNotDataBinable属性设置为false。
固然,OverrideMetadata()函数仅仅是一种重用原有依赖项属性的方法。另外一种重用的方法则是AddOwner()。该函数将其它类型中的依赖项属性添加到当前类型中。该函数的签名以下:
public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata);
该函数用来将一个DependencyProperty添加到ownerType所表示的类型上,并能够经过typeMetadata更改该依赖项属性的行为。
在使用标示依赖项属性的DependencyProperty类型的标记时,咱们最好使用AddOwner()函数所返回的依赖项属性标记,而不是原注册类型中所保存的依赖项属性标记。这样作的最主要目的更可能是基于语义的考虑。实际上,经过本来的依赖项属性标记以及AddOwner()所返回的依赖项属性标记进行操做所返回的运行结果是相同的。
与OverrideMetadata()函数明显不一样的是,该函数并不继承原属性的元数据。所以在使用AddOwner()函数时,软件开发人员最须要考虑的事情就是是否须要自行指定新属性的元数据。固然,若是软件开发人员对基类的依赖项对象调用AddOwner,那么元数据将被继承并和新元数据合并。
引用类型的依赖项属性
实现一个引用类型的依赖项属性与实现普通的依赖项属性的步骤并无什么不一样:定义一个CLR属性包装,并在该属性包装中经过GetValue()以及 SetValue() 函数完成对依赖项属性值的获取和设置。惟一一点不一样的是,软件开发人员不该该在依赖项属性注册的时候为该依赖项属性提供一个默认值,而是在类型的初始化函数中为该依赖项属性显式地赋值。
为何要这样作呢?这是由于在这种状况下,多个实例上的引用类型依赖项属性可能会返回一个相同的引用类型实例。产生该问题的缘由是由依赖项属性的两个特性共同做用产生的:1. 在没有通过赋值的状况下,一个依赖项属性所返回的值就是在依赖项属性注册时传入的默认值。2.在依赖项属性注册过程当中所传入的值其实是引用类型实例的引用,并将做为全部该依赖项属性的默认值,指向同一个引用类型实例。
所以在实现一个引用类型的依赖项属性时,咱们须要在构造函数中显式地为该引用类型的依赖项属性赋值。在这种状况下,您有两种选择:首先查看依赖项属性的类型是否自定义类型,并能够由class更改为为struct。若是不能,那么在依赖项属性注册过程当中将默认值标为null,而在构造函数中再将其设置为所须要的默认值。
第一种方法在WPF实现中很是常见。就以Control类的Padding属性为例:
public static readonly DependencyProperty PaddingProperty = DependencyProperty .Register("Padding", typeof(Thickness), typeof(Control), new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.AffectsParentMeasure));
上面的代码注册了一个类型为Thickness的依赖项属性PaddingProperty,并在该属性的元数据中传入了一个默认值。在查看Thickness类型的定义后能够发现,其其实是一个结构体。在C#中,结构体会在栈上被分配,从而避免了多个该属性所在UI元素引用同一个引用类型实例的状况。
但事情不能老是这么幸运。首先,依赖项属性的类型可能并非一个用户自定义类型,所以咱们并无机会将其转化为结构体。另外,一个类型所包含的信息可能很是多,在那种状况下,将一个类型实现为结构体是并不合适的。所以在必须建立一个引用类型的依赖项属性时,咱们须要在构造函数中对该属性分别赋值。例如ItemsControl就提供了一个ItemsPanel依赖项属性。若是该依赖项属性经过构造函数进行初始化,那么建立依赖项属性的函数调用以及构造函数定义将以下代码所示:
public static readonly DependencyProperty ItemsPanelProperty = DependencyProperty .Register("ItemsPanel", typeof(ItemsPanelTemplate), typeof(ItemsControl), new FrameworkPropertyMetadata(null, ……)); public ItemsControl() { SetValue(ItemsPanelProperty, new StackPanel()); }
可是这违反了WPF对于依赖项属性容器类型构造函数定义的最佳实践。在一个依赖项属性的注册过程当中,以及在派生类对该属性的覆盖过程当中,软件开发人员均可觉得依赖项属性设置回调函数。同时在每次依赖项属性发生变化的时候,这些回调函数都将被执行。因为这些回调函数是在基类的构造函数中被触发,但其所调用的函数可能被派生类重写,因此这些函数的执行可能处于派生类并无彻底初始化的状况。
为了不这种问题,WPF提出了一个定义安全的构造函数的标准:
1. 为您的类型提供一个默认构造函数:
public MyClass : SomeBaseClass {
public MyClass() : base() {
// 全部成员的初始化,包括其它构造函数可能赋值的数据成员或回调函数
// 将会使用的数据成员
}
}
2. 若是一个类型提供了非默认构造函数,那么该构造函数首先须要调用该类型的默认构造函数,而后再使用SetValue()等函数设置各依赖项属性的值。
public MyClass : SomeBaseClass {
public MyClass(object toSetProperty1) : this() {
// 注意,这里调用的是默认构造函数,而不是基类的构造函数
Property1 = toSetProperty1;
}
}
只是谁又能保证用户都熟知这些规则并在编写自定义类型的时候按照这些规则对类型进行编写呢?
若是依赖项属性的类型是一个集合,那么另一点须要注意的地方则是:XAML解析器没法知道如何调用一个泛型函数。也就是说,若是一个依赖项属性的类型是List<T>,那么WPF并不知道如何调用List<T>.Add(T item),而只知道如何调用非泛型接口成员。所以可知若是软件开发人员但愿一个属性是一个集合,那么该集合类型须要实现非泛型的IList接口,如Collection<T>或List<T>。
而在实现一个集合类型的属性时,究竟是将其实现为一个只读依赖项属性仍是可读写依赖项属性则会影响该属性在XAML中的使用方法。就如下面两种XAML标记为例:
<Toolbar> <Toolbar.Items> <ToolbarItem .../> </Toolbar.Items > </Toolbar> <Toolbar> <Toolbar.Items> <ToolbarItemCollection> <ToolbarItem/> </ToolbarItemCollection> </Toolbar.Items> </Toolbar>
固然,上面的XAML代码仅仅是用做示例,而并不是是实际的WPF代码。假设这里的Toolbar类型拥有一个Items属性,其用来记录全部的ToolbarItem类型的子元素。在XAML分析第一段XAML的时候,WPF将首先调用Toolbar.Items属性的get访问符,并依次将该段XAML中所声明的子元素添加到Items属性所记录的集合中。而在分析第二段XAML的时候,WPF将首先建立一个ToolbarItemCollection,并将全部的子元素添加到该集合之中。在该集合建立完毕以后,WPF将调用Toolbar.Items属性的set访问符,以将该集合设置为Toolbar.Items属性的值。
转载请注明原文地址:http://www.cnblogs.com/loveis715/p/4343374.html
商业转载请事先与我联系:silverfox715@sina.com,我只会要求添加做者名称以及博客首页连接。