当前位置:文档之家› Silverlight的依赖属性与附加属性

Silverlight的依赖属性与附加属性

Silverlight 的 依 赖 属 性 与 附 加 属 性
2010 年 3 月 28 日 看看评论评论一个
好久没写 Silverlight 了, 依赖属性 (Dependency Property) 和附加属性 (Attached Property) 这两个算是很基础的知识都不是很记得了。写一写,当做一下笔记吧。
CLR 属 性 与 依 赖 属 性
CLR 属性我们非常熟悉了,在 DotNet 编程中随处可见。最简单最常见的属性访问器就是直接 操纵类的私有成员,如下:
publicclass Person { private String _name; publicstring Name
1
{ get { return _name; } set { _name = value; } } }
C#3.0 对这种常见的写法提供了“自动属性”这一特性,方便了偶等这些懒惰的码农。
publicclass Person { publicstring Name { get; set; } }
这两种写法是等价的,都是需要设立一个实例级的私有变量作为属性访问器的持久存储。这对 需要设立一个实例级的私有变量作为属性访问器的持久存储 需要设立一个实例级的私有变量作为属性访问器的持久存储 于我们非 UI 应用来说没什么。因为第一,我们一般不会创建太多类实例;第二,一个类的属性

通常不会很多,加几个私有变量不会增加系统负担。但是这两个理由对于 UI 应用程序来说恰恰 不成立。
在很多 UI 应用中, 我们经常会创建很多类实例, 成千上万个实例在 UI 系统中是很普遍的事情。 同时,UI 类通常会包含大量的属性供设计人员使用,例如背景颜色,前景颜色,字体,边距等 等, 这些属性在绝大多数情况下会保持默认值, 如果为每个实例都建立这么多的私有变量来存储 UI 属性的值,势必会造成极大的浪费,对系统负担的开销也是不小。
鉴于以上提到的问题,设计一个高效的属性存储系统对于 UI 应用程序的开发是非常重要的。因 此 Silverlight 引入了“依赖属性(DependencyProperty)”。
采用键值对替代成员变量作为属性内部存储
传统 CLR 属性,一个属性对应一个私有变量,UI 元素的属性那么多,创建过多的私有变量不是 一件简单的事情,况且大多数属性只会用到默认值。因此 Silverlight 在每个类实例中使用一个 字典型的成员变量来存放那些用户显式设置的属性(称为 Local Value 本地值),没有设置的 属性就不存。 那属性的默认值存放在哪?既然各个实例的默认值都一样 (不然也不叫默认值了) ,
2
那么直接存放到静态成员变量 (依赖属性的静态成员变量, 而不是注册依赖属性的类的成员变量) 上就行了。这也就大大提高了存储的效率。
在实现上,Silverlight 中所有的 UI 元素都继承自 DependencyObject,这个类封装了对依赖 属性的存储以及访问等操作。
注册依赖属性
既然依赖属性采用键值对这样的哈希结构进行存储, 那么要获取不同属性的值, 我们就必须使用 不同的哈希键,否则就会读取到其他属性的值了。因此,当我们在向 Silverlight 属性系统注册 依赖属性的时候, Silverlight 会返回一个唯一的属性标识对象, 类型为 DependencyProperty。 我们以后就通过这个唯一标识对象去访问依赖属性的值。
由于这个唯一标识符是所有类实例都公用并且不会被修改的,因此我们通常将其保存到一个 static readonly 的成员变量中。

DependencyProperty 类提供了两个方法,一个是 Register 方法,用于注册依赖属性;另外 一个是 RegisterAttached,用于注册附加属性,这个后面再讲。
publicstatic DependencyProperty Register( string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata )
Register 方法的签名由几部分组成, Name 参数指明了依赖属性使用的名称, 这个名字很重要, 的时候, 在定义控件 Style 和 Template 的时候,Setter 的 Property 属性填入的值就是注册依赖 属性时使用的名称;propertyType 指明了依赖属性实际的类型,ownerType 指明了是哪个类 属性时使用的名称 注册了此依赖属性,最后 typeMetadata 存放了一些依赖属性的元信息,包括依赖属性使用的 默认值,还有属性值发生变更时的通知函数。
3
属性的存取
和 CLR 属性不同,依赖属性不是直接对私有变量的操纵,而是通过 GetValue 和 SetValue 的 方法来操作属性值的。
下面的代码演示了为 Ball 控件设置一个 Center 的依赖属性,并且在程序中读取和修改此属性 的过程:
publicclass Ball : Control { publicstaticreadonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null); }

publicclass BallApp { publicvoid RollBall(Ball ball) { Point curCenter = (Point)ball.GetValue(Ball.CenterProperty); curCenter.X++;
// 注意对值类型对象操作完毕之后一定要调用 SetValue 修改才能生效 ball.SetValue(Ball.CenterProperty, curCenter); } }
由于上述对依赖属性的操作经常需要涉及到类型的转换,比较麻烦,而传统 CLR 属性用起来和 直接操纵普通变量一样方便,因此通常在设计依赖属性的时候,都会使用 CLR 属性将其包装起 来,我们称之为增强型的 CLR 属性。
4
publicclass Ball : Control { publicstaticreadonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null);
public Point Center { get { return (Point)GetValue(CenterProperty); } set { SetValue(CenterProperty, value); } } }
按照约定,依赖属性的名称通常是相应 CLR 属性名称后面加上个“Property”字符串。
事实上,使用 CLR 包装依赖属性并不只是为了方便 (https://www.doczj.com/doc/e116224848.html,/en-us/library/cc221408%28VS.95%29.aspx#back_

dependency_properties), ),很多依赖于 CLR 属性作为基础的工具或者子系统并不能直接访 问依赖属性,而只能通过 CLR 属性去间接访问依赖属性。例如上面的例子中, ,假设我们并没有 设置一个 Center 的 CLR 属性 属性,那么以下的 Xaml 将会编译失败,因为 Xaml 解析器无法知道 Ball 类有一个 Center 的依赖属性 (在 Style 中设置 Center 属性值就可以编译成功 因为 Style 属性值就可以编译成功, 是动态查找属性的)。

依赖属性的寻值逻辑和值变更通知
上面提到的只是依赖属性相比 CLR 属性在存储效率的不同,实际上,依赖属性还有其他实用的 依赖属性还有其他实用的 特性。
寻值逻辑
CLR 属性在获取值的时候是直接读取成员变量值返回的 而依赖属性在使用的时候是通过 属性在获取值的时候是直接读取成员变量值返回的,而依赖属性在使用的时候是通过 GetValue 函数的调用来获取属性的值 函数的调用来获取属性的值。实际上,GetValue 内部做的事情可不止是简单的读取
5
字典里头存放的值。他还有寻值逻辑 他还有寻值逻辑。如下图所示:
当你调用 GetValue 去读取一个依赖属性的值的时候 去读取一个依赖属性的值的时候,Silverlight 的属性系统会首先从动画系 统中查找当前是否有作用在此依赖属性上的动画, 统中查找当前是否有作用在此依赖属性上的动画 如果有, 则返回此动画值。 从这里也可以看出, 从这里也可以看出 依赖属性是 Silverlight 实现动画机制的基础 实现动画机制的基础。注意,如果动画已经停止了,并且没有设置 并且没有设置 FillBehavior=HoldEnd 的话 的话,那么 Silverlight 就不会返回此动画值。

如果读不到动画值,那么 Silverlight 就会尝试读取本地值。本地值有几种类型,一种是用户通 过代码或者 Xaml 直接设定的值。一种是通过资源绑定得到的值,最后一种是通过数据绑定得 到的值。这些都被视为本地值。
资源数据绑定文本
如果还是读取不到,那么就继续尝试读取控件模板和样式中设置的值。
如果所有这些值都读取失败,那么 Silverlight 属性系统就会返回该依赖属性的默认值。当我们 注册依赖属性的时候,可以传入一个 PropertyMetaData 对象,这个对象包含了此依赖属性的
6
默认值和值变更通知回调函数。如果注册的时候没有传入默认值,则对于引用类型的依赖属性, 返回 null, 对于字符串, 返回 String.Empty, 对于值类型, 则返回一个以默认值初始化的实例。
这里需要对集合类型特别注意,由于通过 PropertyMetaData 传入的默认值是所有类实例共享 的,因此,一定要在类构造函数中显式传入集合的实例。
publicclass GameRoom : Control { public List Balls { get { return (List)GetValue(BallsProperty); } set { SetValue(BallsProperty, value); } }
publicstaticreadonly DependencyProperty BallsProperty = DependencyProperty.Register("Balls", typeof(List), typeof(GameRoom), null);

public GameRoom() { Balls = new List(); } }
可能正是因为 Silverlight 的依赖属性在获取值的时候需要从多个地方去读取值, 而不是像 CLR 属性一样,直接从成员变量中读取值,所以才被称之为“依赖”属性吧。
值变更通知
属性值的变更通知我们并不陌生。我们在 DotNet 中实现的时候,一般是让类实现 INotifyPropertyChanged 接口。在 UI 系统中,值变更通知是经常需要用到的。数据源一旦变 更,所有相应的 UI 元素的值都要相应的做出调整。Silverlight 的依赖属性对此有内置的支持。 只要你在绑定时使用依赖属性, 那么当依赖属性值发生变更的时候, 所有绑定的地方的值都会同 步更新。而且,依赖属性也提供了一个值变更通知函数(在注册依赖属性时通过
7
PropertyMetaData 传入),你可以自定义一个函数来控制值变更时需要执行的操作。
publicclass Ball : Control { publicstaticreadonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(Ball), new PropertyMetadata(OnCenterChanged));
public Point Center { get { return (Point)GetValue(CenterProperty); } set { SetValue(CenterProperty, value); } }

privatestaticvoid OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Ball ball = d as Ball; // 获取新的球心 Point newCenter = (Point)e.NewValue; // ... } }
Silverlight 的 附 加 属 性 ( Attached Property) ——全 局 的 依 赖 属 ) 全 性
刚才提到的依赖属性和 CLR 属性一样都是服务于某一个类的 只不过将属性改造得存储上更有 属性一样都是服务于某一个类的。只不过将属性改造得存储上更有 效率,使用上更加强大。在 Silverlight 中还有一种特殊的依赖属性,这种依赖属性并不只是服 这种依赖属性并不只是服 务于某个特定的类,而是服务于全局 而是服务于全局,这就是附加属性。从名字上也可以看出来 从名字上也可以看出来,附加属性是在
8
某个类里面注册,然后可以被其他类所使用 然后可以被其他类所使用。
什么情况下需要使用附加属性呢?举 Canvas 类的 ZIndex 属性作为例子。 什么情况下需要使用附加属性呢
容器类在叠加子控件的时候, ,需要考虑哪个控件放置在最上面,那个放在下面。 。
那么容器类怎么知道子控件的叠放顺序呢?最不动脑子的设计就是为所有的控件都添加一个 那么容器类怎么知道子控件的叠放顺序呢 最不动脑子的设计就是为所有的控件都添加一个 ZIndex 的属性,属性的值代表叠放的顺序 属性的值代表叠放的顺序。但这样的后果就是,如果我这个控件不参与布局 如果我这个控件不参与布局, 那多这个属性就会显得很浪费。 那多这个属性就会显得很浪费 所以比较理想的设计是, 需要用到这个属性的时候就有这个属性, 需要用到这个属性的时候就有这个属性

不需要的时候就没有这个属性的负担。 附加属性的出现就是为了解决这样的问题。 一旦控件需要 某个属性的时候,我们可以把这个属性附加到这个控件类上。
注册附加属性和依赖属性差不多,只不过函数名为 RegisterAttached。这个就不多说了。
什么时候应该用到依赖属性
既然依赖属性那么高效, 而且那么强大, 那么我们是不是应该保持使用依赖属性的习惯呢?事实 上,任何好处都是有代价的。Silverlight 的依赖属性在访问效率上并不如直接访问成员变量那 么高效。因此,对于那些比较简单而访问频率又非常高的属性,建议还是使用传统的 CLR 属性 去实现。
暂时想到这么多了,以后有新的认识再补充补充。
——Kevin Yang
9

相关主题
文本预览
相关文档 最新文档