见识propertymetadata

假如你尝试过自己定义一个dependencyproperty,你一定会发现在dependencyproperty.regist方法中可以传入一个propertymetadata类型的对象,这就是属性的"metadata"。假如你对.net框架比较了解,你对"metadata"这个词应该不生疏,简单地说,metadata就是一个用来描述对象自身的对象,同理,这里的metadata也就是我们用来描述dependencyproperty本身的东西。

这么说比较抽象。看些具体的东西吧。

dependencyproperty.register("custom"typeof(string), typeof(window), new propertymetadata("hello"));

我们注册了一个name为custom的dependencyproperty,这里的new propertymetadata("hello")创建了一个元数据,"hello"参数代表“默认值”。顾名思义,我们给这个属性定义了一个默认值,这应该很好理解了吧。假如你接触过asp.net控件开发,你是不是会想到在属性前使用attribute(特性,俗称“方括号”)定义默认值?没错,使用attribute其实就是在.net属性的元数据中添加内容,在使用dependencyproperty时就不用这么麻烦,我们把new一个metadata传进去就ok了。好吧metadata就那么简单,下面我们看看propertymetadata这个对象除了能定义属性的defaultvalue之外还能做什么。

还有两个属性coercevaluecallback和propertychangedcallback,这两个属性类型都是delegate,也就是可以传入方法的委托,coercevaluecallback用于定义“强制”属性值时执行的方法,propertychangedcallback用于定义属性值发生变化是执行的方法。具体的用法可以参考msdn,这里不多说。

这里我想解释一下调用dependencyobject的getvalue方法获得属性值时是如何受到metadata的影响的。

我曾经提到过dependencyproperty的“优先级别”,msdn链接:dependency property value precedence

msdn这篇文档中列举了10个获得值得优先级别,其中拥有最高优先级的是"property system coercion",而拥有最低优先级的是default value from dependency property metadata,你应该发现,这两项正是在metadata中的coercevaluecallback和defaultvalue属性。
可以想象一下需要获得一个值时的执行过程,系统先从最低优先级别的项(defaultvalue)开始,按优先级由低到高逐个尝试得到值,假若在某一步能得到值,则覆盖当前的值
优先级最高的是"property system coercion",我们看一个msdn中一个简单的coercevaluecallback例子:

private static object coercecurrentreading(dependencyobject d, object value)
{
  gauge g 
= (gauge)d;
  
double current = (double)value;
  
if (current < g.minreading) current = g.minreading;
  
if (current > g.maxreading) current = g.maxreading;
  
return current;
}
 

优先级最高,也就是最后才尝试这个方法,我们可以这么理解,之前已经通过各种方法尝试取得一个值了,现在把这个值传到这个方法里来,做一次最后的检查,就好象这里的value,不论前面通过什么方式设置了值,都需要在最后这里限制范围。

至于propertychangedcallback就不多说了,实现了一个“属性改变通知”功能。

小结一下,metadata利用defaultvalue和coercevaluecallback为dependencyproperty提供了最基本的数据保证和最后的数据把关。在这里建议大家定义自己的dependencyproperty时一定要提供defaultvalue。究竟是“基本保证”。

有一个细节需要提到,metadata虽然可以在注册dependencyproperty时候设置,但是千万不要以为一个metadata是对应一个dependencyproperty对象,这也许不好理解,他不是“属性”的metadata么?实际上,metadata是按照一个dependencyproperty对应不同dependencyobject存储的,可以这么理解,单个dependencyproperty并非一个“属性”,只要一个dependencyobject和一个dependencyproperty在一起,才组成一个真正的“属性”(还记得系统全局索引dependecyproperty使用的hash key吗? "this._hashcode = this._name.gethashcode() ^ this._ownertype.gethashcode();" )
因此通过单个dependencypropery你只能获得"defaultmetada",正确获得metadata方法是使用dependencyproperty.getmetadata方法,他需要传入一个dependencyobject对象。

wpf专用——frameworkpropertymetadata

metadata除了上面这些基本功能外,就是为特定环境下提供特定的功能服务,前面提到过,这里的dependencyproperty是专门为wpf服务的,自然也就有专门为wpf服务的metadata,也就是frameworkpropertymetadata。他继续于上面提到的propertymetadata,加入了许多可定义的属性,专门用来处理wpf界面显示时所需要的一些功能。

p.s. 假如你对wpf框架比较熟知,你一定不会对frameworkelement生疏吧,凡是涉及到ui的东西,wpf里都会给一个framework前缀,呵呵。

前面提到过很多wpf的特性,比如第二篇post中提到的“值继续”“自动的进行重新布局”都和frameworkpropertymetadata中的设置有关,具体的设置在msdn中。(frame property metadata)已经讲的非常具体了,在这里我只简要的说一些吧。

  • wpf的layout引擎中有measure,arrange,render三个方法,假如你认为当你定义的dependencyproperty值变化时界面上应当调用相应的方法(例如改变background时需要重新render),那么你以考虑在metadata中设置affectsarrange, affectsmeasure, affectsrender这些标志;
  • 假如属性值变化时不仅自己需要调用相应的方法,还需要自己的父对象调用相应的方法(例如改变size时需要同时改变父对象的size),那么可以考虑在metadata中设置affectsparentarrange, affectsparentmeasure这些标志;
  • 属性值继续。我认为这是wpf中的属性系统中最漂亮的一个功能设计;

另外frameworkpropertymetadata也为wpf的数据绑定操作提供了一些功能,还有为navigatewindow提供的功能等,就不具体说了。
看一下frameworkpropertymetadataoptions的枚举值就知道,他实现的功能太多了。可以这么说,前面的post中讨论过的dependencyproperty的属性值存储等机制提供了一个基础,而真正利用这个基础实现的功能,似乎都通过frameworkpropertymetadata实现了(现在的wpf dependencyproperty果真完完全全是为wpf框架提供的)。

关于"attachedproperty"

在第三篇post(wpf/silverlight为什么要使用canvas.setleft()这样的方法?)中,我已经涉及到了关于attachedproperty的介绍和使用,这里就不再赘述了。其实假如你已经完全理解了dependencyproperty中值的存储机制,也许已经不会再对“attachedproperty”这个希奇的东西感到疑惑了。

我们先回忆一下dependencyproperty中值的存储机制的几个要害点(具体的讨论请参考wpf里的dependencyproperty(4)):

  • 每个dependencyproperty都是一个dependencyproperty对象,可以通过dependencyproperty.register静态方法获得;
  • 系统中有一个全局静态的hashtable,通过dependencypropery的ownnertype和name两个键值对索引取得某个具体的dependencyproperty;
  • 每个dependencyobject中有一个非静态的hashtable,可以通过一个dependencyproperty索引取得某个值。

注重第三点,每个dependencyobject中作为索引的dependencyproperty假如定义在自身内部,那么很好理解,但是假如这个dependencyproperty定义在其他的dependencyobject中呢?这就是attachedproperty了。

举个最简单的例子,假设我们有如下的语句:

<button name="button1" canvas.left="100" >button</button>

这里的leftproperty定义在canvas类,不过却在button的实例中调用,这正是attachedproperty。

你也许会有疑问,假如是这样的话,"attachedproperty"和一般的"dependencyproperty"不是没有区别吗?虽然很希奇,不过的确是这样的,我认为"attachedproperty"只是microsoft为了让大家更好理解“为不定义在自己身上的属性赋值”这个行为造出来的词吧。事实上,虽然msdn中说明需要使用dependencyproperty.registerattached方法来定义attachedproperty,但是你可以大胆尝试一下,假如你使用dependencyproperty.register方法也是没有问题的。不过还是推荐大家按照msdn中说的做,我肯定在某些细节处register方法和registerattached方法还是不同的(adam nathan在"windows presentation foundation unleashed"中提到,registerattached对metadata做了某些优化,具体的我也不是很清楚)。

说到这里,这种复杂而希奇的设计大概已经把你弄得有些晕了吧。呵呵,不过理解了还是很有意思的。下面说一下最后一种同样有些希奇的方法。

使用addowner方法注册属性

我们先看一个现象,先看这句代码。

<button fontsize="20">test</button>

这很简单是吧,那这样呢:

<button fontsize="20">
     
<textblock>test</textblock>
</button> 

定义的textblock同样可以继续到fontsize="20"这个属性,这正是“属性值继续”的特性。我们先不考虑“属性值继续”,我们考虑一下textblock.fontsizeproperty和button.fontsizeproperty是不是一个dependentyproperty呢?也许你的第一反应“肯定不是”,假如不是,那么为什么button.fontsizeproperty的设置会影响到textblock呢?

答案确是“是”,他们确实是同一个dependencyproperty。不信的话,你可以试试看下面的代码:

<button textblock.fontsize="20">button</button>

同样设置了button的fontsize,很有意思吧。为什么会这样呢?

其实真正的fontsizeproperty属性既没有定义在button中,也没有定义在textblock中,而是定义在textelement对象中的,通过reflactor可以看出来,button和textblock中使用了这样的语句来定义fontsizeproperty(button中的fontsizeproperty继续自control.fontsizeproperty):

fontsizeproperty = textelement.fontsizeproperty.addowner(typeof(textblock));

这是什么意思呢?再次回忆dependencyproperty值得存储机制,其实addowner方法就是为已经定义的dependencyproperty在全局静态的hasttable中生成了一个新的key,这个key使用原来dependencyproperty的name,但是用了新的ownertype。重点是,这里产生的仅仅是一个新的key,并没有生成一个新的dependencyproperty对象,换句话说,新旧两个key都指向了同一个dependencyproperty对象。这样也就很好理解为什么textblock.fontsize能设置button的fontsize了。

假如你还是不相信,你可以直接在程序中使用 bool f = (textblock.fontsizeproperty == button.fontsizeproperty) 语句试试看,返回的是true,这表明他们指向的是同一个dependencyproperty。

实质上,addowner方法生成的dependencyproperty可以看成是attachedproperty的一种变形,假如没有addowner方法,也许我们需要把所有的fontsize都得写成textelement.fontsize,这显然会更加令人费解。因此这是一个很有用的功能,最经典的场景大概就是解决属性继续中的问题吧,就像上面的例子,你只需要在window对象或button对象中设置fontsize,他的子元素继续到值后就都知道需要做什么,不需要再去考虑设置的到底是button.fontsize还是window.fontsize或者是textblock.fontsize。

posted on 2010-05-17 19:20  Memory  阅读(1297)  评论(0编辑  收藏  举报