在c#中--得益于c#3.0中的自动属性--我们可以十分轻松的通过如下代码创建一个叫做“AuthorName”的属性。
public string AuthorName { get; set; }
上面的代码就是我们熟悉的CLR属性,我们可以很方便的读/取这个属性的值。不过在silverlight的世界中如果你想做更多更牛的事情,CLR属性就显得有些力不从心了。它们包括--动画、数据绑定、样式/模板等等。
因此,微软在WPF中提出了依赖属性(Dependency Property,以下简称DP)的概念并将其延伸至Silverlight平台。在这篇文章我将介绍DP在Silverlight的定义和使用。
要了解一件新事物最简单的方式就是找出一样我们熟悉的,与要介绍的新玩意相似的东西来做对比:将上文的AuthorName属性定义为DP的方式是如下这样的
-
public string AuthorName { get { return (string)GetValue(AuthorNameProperty); } set { SetValue(AuthorNameProperty, value); } }
-
代码片段1
-
public static readonly DependencyProperty AuthorNameProperty = DependencyProperty.Register("AuthorName", typeof(string), typeof(AuthorTextField), new PropertyMetadata(""));
-
代码片段2
-
哇!完成一个DP的定义需要这么多的代码,kidding me?
先别急,下面我们就来了解一下这些代码的含义。
首先,要构造一个DP,我们需要准备两样事
通过CLR属性包装
将DP用代码片段1中所示代码包装起来后,我们便可以轻松的像CLR属性一样设置和获取DP的值。因为这个包装用的就是标准的CLR声明方式,只是他的get和set中有一些简单的逻辑而已罢了。这里我们需要分别使用GetValue和SetValue方法获取或这是一个叫做AuthorNameProperty属性的值(没错,这是个DP。Silverlight中约定DP要以-Property词缀结尾)。
定义依赖属性(即本文中的AuthorNameProperty)
现在来看代码片段2,这里我们将其分成若干部分来解释:
public static readonly DependencyProperty AuthorNameProperty
代码片段3
先看看这部分,在定义DP时我们通常需要的返回类型是DependencyProperty(另外一种式附加属性AttachedProperty),而它的修饰符是public, static, 和 readonly。
然后我们看接下来的部分:
DependencyProperty.Register("AuthorName",
typeof(string),
typeof(AuthorTextField),
new PropertyMetadata(""));
-
代码片段4
- 这段代码的作用是注册DP并为其添加一些标识,以便Silverlight可以使用它。
这样看来定义一个DP其实并不困难,通常我们只需按部就班的按照约定和格式完成定义而不需了解这背后可能令人头疼的具体细节--Silverlight的底层属性机制会确保这些DP在应用程序中的各尽其责。
现在,我们应该对DP的定义有了一个大体的了解。下面我们会将注册DP的代码部分(即代码片段4)进行详细剖析。
我们先来看一下Register()方法的定义
public static DependencyProperty Register(string name,
Type propertyType,
Type ownerType,
PropertyMetadata typeMetadata);
代码片段5
Register()方法接受三个参数
- name:故名思意,你要注册的DP的名称。这里建议你将其与你定义的DP名称保持一致(如例子中的AuthorName)。
- propertyType:DP的类型,在本例中为string。
- ownerType:DP所属类的类型,这将决定DP的作用域。在本例中我们假设它的作用域是一个叫做AuthorTextField的用户控件。
- typeMetadata:通过PropertyMetadata类为该DP定义元数据。有很多silverlight开发者觉得这些元数据的定义没什么用,其实这种想法是错误的。元数据会对你在应用程序中使用DP产生很大的影响。比如我们可以通过元数据定义这个DP的默认值、定义当DP发生改变时引发的特定方法--抑或将这两样一齐定义,看一下PropertyMetadata的构造函数定义就知道了:
public class PropertyMetadata { public PropertyMetadata(object defaultValue); public PropertyMetadata(PropertyChangedCallback propertyChangedCallback); public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback); public object DefaultValue { get; } }
代码片段6
下面我们来做一个试验以便了解注册依赖属性时附加元数据的能力。
在没有默认值的情况下,我们在Expression Blend中观察这个AuthorName属性:
接下来我们做如下定义
new PropertyMetadata("Sir Arthur Conan Doyle")
同样打开Expression Blend进行围观:
与我们通过PropertyMetadata构造函数构造的默认值相同。
由于在我们的例子中,AuthorName的类型是string,所以我们PropertyMetadata构造函数中的设置其默认值的类型也是string。而在上面我们已经知道,PropertyMetadata构造函数中接受的参数是object,所以在具体应用中我们应该根据实际情况决定传入的默认值(对象)。
另外一个可以指定给PropertyMetadata的参数对象是一个回调方法。这个方法将在DP的值发生变化时被调用。举个例子
代码片段7
在上面的代码中,我们传入了一个类型为PropertyChangedCallback并命名为AuthorNameChanged的回调函数。然后,理所当然的,我们需要定义这个函数。
-
private static void AuthorNameChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) { // 一切皆有可能! 024hi.cnblogs.com 紫色永恒 }
代码片段8
由于我们定义的DP(以及注册DP的代码)是静态的,所以这里定义的事件(AuthorNameChanged)也必须是静态的。
这个回调方法需要传入两个参数。第一个参数是对具体DependendyObject的引用,第二个参数是特定委托的参数集DependencyPropertyChangedEventArgs。
有了静态修饰符及这两个参数,我们的AuthorNameChanged方法就定义完成了。每当AuthorName这个DP发生变化时,这个方法都会被调用。
最后,根据代码段6的定义,我们可以将定义默认值及回调函数的方式通过同一个构造函数传入。
DependencyProperty.Register("AuthorName",
typeof(string),
typeof(AuthorTextField),
new PropertyMetadata("Sir Arthur Conan Doyle", new PropertyChangedCallback(AuthorNameChanged)));
Okay!Have fun~