依赖属性(Dependency Properties)
WPF引入了一种新的属性类型,称作“依赖属性”,它可以用在外观风格、自动化数据绑定以及动画等方面。我们在第一次遇到这个概念的时候,可能会有一些迷惑,因为它把拥有简单字段、属性、方法和事件的.NET类型弄得有点复杂。但是,当我们理解了它可以用来解决什么样的问题时,就会非常喜欢它。
依赖属性依赖多个能够在任意时刻及时确定属性值的提供器(provider),这些提供器可以是一个不断改变属性值的动画,也可以是一个可以将属性值传递到子元素的父元素。它的最大的特点无疑是它能够提供变更通知(change notification)的能力。
为属性加入这种能力,可以使得我们在XAML中直接使用WPF的各种丰富功能。WPF“说明性友好”设计的关键就是为了支持对属性的大量使用(这就是说,XAML就像XML,可以为元素设置各种各样的属性,“说明性友好”的目的就是追求有效地用利属性),比如Button就拥有96个公共属性!XAML可以简单地设置属性而无需任何程序代码,但是如果在依赖属性中没有额外的引擎,那么即使在没有编写代码的情况下想要获得属性值都是困难的。
理解依赖属性的细节通常只对自定义控件开发者来说比较重要。有时候,我们可能仅需要知道风格类和动画类的依赖属性。在开发一段时间WPF应用后,我们可能发现,我们甚至希望所有的属性都是依赖属性!
依赖的属性的实现
实际上,依赖属性就是连接了一些WPF基础结构的一般.NET属性。它全部通过WPF API完成,对任何.NET语言(除了XAML)来说是完全透明的。
例:标准依赖属性的实现(Button的IsDefault)
public class Button : ButtonBase
{
// 依赖属性
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
// 注册属性
Button.IsDefaultProperty = DependencyProperty.Register("IsDefault",
typeof(bool), typeof(Button),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsDefaultChange)));
// ……
}
// .NET属性包装器(可选的)
public bool IsDefault
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
// 属性变更回调(可选的)
private static void OnIsDefaultChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e) { }
// ……
}
静态的IsDefaultProperty字段是真正的依赖属性,它是System.Windows.DependencyProperty类型的。所有的依赖属性习惯上以Property为后缀,且其访问权限是pulbic和static。依赖属性一般由DependencyProperty.Register方法创建,它需要一个名称(IsDefault)、一个属性类型(bool)和声明拥有这个属性的类型(Button)。通过该方法的不同重载,我们可以有选择地传递处理自定义属性元数据,也可以编写那些用于处理属性值变更、强制转换(coerce)和验证(validate)的回调方法。Button在其静态构造方法中调用了Register方法的一种重载,并向其提供了依赖属性的默认值(false),并且为处理变更通知(change notification)附加了一个委托。
IsDefault属性最终通过调用继承自依赖属性基类System.Windows.DependencyObject的GetValue和SetValue方法实现了对IsDefaultProperty的访问器。GetValue返回由SetValue最后一次设置的值,或是当SetValue从未被调用时,返回属性注册时提供的默认值。IsDefault属性有时被称作“属性包装器”(property wrapper),它不是必须的,如Button的使用者可以直接调用公共方法GetValue和SetValue来获取以来属性的值。尽管如此,定义一个.NET属性仍旧十分有用,因为它不但令我们以编程方式读/写字段更加自然,而且还令XAML可以直接设置属性。(意思是,如果在元素对象的类型中定义了.NET属性,则我们在XAML中操作元素的属性,就如同在元素对象上操作那个相应的.NET属性;相反地,如果我们没有定义.NET属性,那么在XAML中也无法简单通过操作元素的属性来调整元素对象的状态,因此最好为依赖属性定义对应的属性包装器。)
在XAML在使用依赖属性的时候需要注意,尽管XAML编译器在编译时需要属性包装器,但是在运行时,WPF并不使用属性包装器,而是直接调用底层GetValue和SetValue方法。因此,在依赖属性对应的包装器中,除了调用GetValue和SetValue之外,不要包含任何逻辑。所有的WPF内建属性包装器都遵循这条规则,因此在我们编写新的包含依赖属性的类型时,也应当这么做。
像上例那样表示一个简单布尔值的属性,表面上看起来有些啰嗦,但由于GetValue和SetValue内部使用了一个高效的存储结构(意思大概是说,依赖属性通过Register方法注册到一个数据结构中,这个结构的存取效率很好,因此通过那两个方法操作依赖属性,相应的开销也会很小),且IsDefaultProperty是静态字段,这使得依赖属性的实现相对于一般的.NET属性而言,由于不用为每个实例都分配内存,从而节省内存的开销。
依赖属性带来的好处不仅仅在内存使用方面,它还集中化并且标准化了许多属性的实现代码,这些属性实现代码有用来检查线程访问的,也有用来引发重绘所含元素的。例如,在元素属性值变更的时候(如Button的Background属性),元素需要被重绘,这可以通过传递FrameworkPropertyMatadataOptions.AffectsRender标志到重载的DependencyProperty.Register方法来实现。此外,依赖属性还允许三种重要的特性:变更通知、属性值继承以及对多种提供器的支持。