zoukankan      html  css  js  c++  java
  • DependencyProperty

    见识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。

  • 相关阅读:
    hdu_5855_Less Time, More profit(二分+最大权闭合图)
    hdu_5832_A water problem(模拟)
    poj_3261_Milk Patterns(后缀数组)
    [bzoj1072][SCOI2007]排列(状态压缩DP)
    [bzoj1597][USACO2008]土地购买(DP斜率优化/四边形优化)
    [bzoj1293][SCOI2009]生日礼物(单调队列)
    [bzoj 2463]谁能赢呢?(博弈论)
    矩阵快速幂优化递推总结
    [bzoj1563][NOI2009]诗人小G(决策单调性优化)
    [bzoj1821][JSOI2010]部落划分(贪心)
  • 原文地址:https://www.cnblogs.com/Memory/p/1737621.html
Copyright © 2011-2022 走看看