什么是依赖属性
依赖属性是WPF引入的一个新的属性类型,用来对样式、数据绑定、动画、属性更改通知等提供支持,依赖属性是一种可以自己没有值,但是可以通过Binding从数据源获得值(即依赖在别人身上)的属性。与传统的CLR属性相比,依赖属性的新颖之处在于:
- 节省实例对内存的开销
- 属性值可以通过绑定依赖在其他对象上
在传统.NET开发中,一个对象所占用的内存空间在用new操作符创建实例时就已经确定了,而WPF允许对象在被创建时不包含用于存储字段数据的空间,只保留在需要用到数据时能够获得默认值和能够借用其他对象的数据或者实时分配空间的能力。WPF创建的对象成为依赖对象,它是依赖属性的宿主,它依靠依赖属性来实现实时获取数据的能力。也就是依赖属性与依赖对象结合起来才能形成完整的Binding目标被Binding源的数据所驱动。
在WPF中依赖对象与依赖属性分别有DependencyObject类和DependencyProperty类实现。DependencyObject包含GetValue()和SetValue()两个方法:
public class DependencyObject : DispatcherObject { public DependencyObject(); public object GetValue(DependencyProperty dp); public void SetValue(DependencyProperty dp, object value); //... }
这两个对象都有一个DependencyProperty对象的参数dp,GetValue()通过dp获取值,SetValue()通过dp存储值。
DependencyObject继承层次结构如下:
通过继承结构可以看出DependencyObject是相当底层的一个基类,WPF的所有UI控件都是依赖对象。
声明依赖属性
由于DependencyProperty必须以DependencyObject为宿主,才能借助DependencyObject的GetValue()与SetValue()进行读取与写入。因此要想使用自定义的DependencyProperty,宿主类一定要是DependencyObject的派生类。
在VS中使用CodeSnippet功能,键入propdp然后敲两下Tab键,就可以自动生成带CLR属性包装的依赖属性,继续按Tab键可以修改它的各个参数。
public class Student:DependencyObject { public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } } // Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc... public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student), new PropertyMetadata("")); }
DependencyProperty的实例NameProperty并非使用new操作符得到而是通过密封类DependencyProperty的静态方法Register注册得到的。除了上面四个参数的版本外,Register方法还有另外两个重载版本:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType); public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);
其中:
- name参数用来指定以哪个CLR属性作为这个依赖属性的包装器;
- propertyType用来指定该依赖属性存储什么类型的值;
- ownerType用于指定此依赖属性的宿主是什么类型;
- typeMetadata用于给依赖属性的DefaultMetadata赋值;
- validateValueCallback是用于验证依赖属性值的回调。
如何存取值
作为初学者,最容易混淆的概念就是误认为Name属性就是依赖属性,其实Name属性是一个CLR属性,它只不过是依赖属性的包装器,真正的依赖属性是有public static readonly 三个修饰符修饰的NameProperty。
并且有没有Name这个包装器,依赖属性都存在,包装器的作用就是以实例属性的形式向外界暴露依赖属性,有了这个包装器,一个依赖属性才能成为Binding数据源的一个Path。
从代码可以看出对Name这个的CLR属性存取值,实际上就是调用DependencyObject的SetValue()和GetValue()来间接地对依赖属性存取值,这也是问什么称Name为包装器的缘故。关键的问题来了,由于依赖属性NameProperty是一个static对象,调用SetValue()赋值是时,值是不可能保存在这个静态对象里的,那么值到底被存储到哪里去了呢? 刘铁猛老师的《深入浅出WPF》在 "7.2.3 依赖属性存取值的秘密" 一节 深入剖析了依赖属性存取值的内部原理,并最后总结了DependencyProperty对象的创建与注册:
"创建一个DependencyProperty实例并用他的CLR属性名和宿主类型名生成唯一的hash code,最后把hash code 和DependencyProperty实例作为键-值对存入全局的、名为PropertyFromName的Hashtable中。这样,WPF属性系统就能通过CLR属性名和宿主类型名从这个全局的Hashtable中检索出对应的DependencyProperty实例,对其进行存储、访问、修改、删除等操作"。