Dependency Properties
WPF引入了一种新型的属性:Dependency Property(以后简称DP).这个属性贯穿WPF的始终。它提供了对样式,自动数据绑定,动画等特性的支持。第一次遇到这个概念时,您可能会对它的合理性产生怀疑:DP的出现破坏了.NET类型的简单性,例如.NET原有的成员,属性,方法和事件都是非常简洁的(fields, properties,methods and events).但当您了解到DP所解决的问题之后,你就会接受它了。
一个DP的值可以由多个提供者提供,这样可以在多方面及时地为其赋值。这些提供者可以是一个animation:连续不断地改变DP的值;也可以父元素:将值传递给它的子集元素等等。可以论证地,DP的一个最大的特性就是其内置的变更通知功能(Change Notification)。
让属性拥有如此智能的特性的目的是为了将其应用到XAML中。WPF声明友好式(declarative-friendly)的设计的核心是为了重用这些属性。以Button控件为例,它一共有96个公共属性。在不添加任何程序代码的情况下,我们就可以在XAML中使用这些属性(或者通过设计工具).但是如果没有了DP中的底层实现,那么在不编写程序代码的情况下,就很难实现了。
我们首先简单的看一下DP的实现,以便有针对性地对其进行讨论。然后再对“DP对.NET属性上的增强功能”进行深入研究:
- Change notification
- Property value inheritance
- Support for multiple providers
如果要创建自定义控件,那么了解各个DP之间的细微差别是非常重要的。对于那些WPF的使用者来说,他们也需要知道什么是DP,以及DP是如何工作的。例如你可以只使用style和animate的DP。但您使用了WPF一段时间后,就会愿意将所有的属性都设置成DP了。
实际上,DP只是在.NET的正常属性添加上了额外的WPF底层结构。这些都是由WPF的API完成的。没有任何.NET语言(XAML除外)可以理解 DP。在List3.3中,示范了Button控件是如何有效创建IsDefault这个DP的:
public class Button : ButtonBase
{
// The dependency property
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
// Register the property
Button.IsDefaultProperty = DependencyProperty.Register(“IsDefault”,
typeof(bool), typeof(Button),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsDefaultChanged)));
…
}
// A .NET property wrapper (optional)
public bool IsDefault
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
// A property changed callback (optional)
private static void OnIsDefaultChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e) { … }
…
}
静态的成员IsDefaultProperty就是一个DP,类型为System.Windows.DependencyProperty.根据管理,所有的DependencyProperty成员都是公共类型和静态类型的(public and Static),并且要以“Property”字符串结尾。DP通常会用DependencyProperty类的静态方法Register方法创建。 Register方法需要以下参数:DP的名称(例子中的IsDefault),属性类型(例子中的bool),该属性类型的所有者(Button)。 Register还有一些重载方法,您可以将一些元数据传递给这些方法。这些元数据可以决定属性如何被WPF对待,是否当属性值改变时进行回调,强制限定属性值或者验证属性值。List3.3的Button控件就调用了其中的一个重载。为DP设置了默认值,并且设置了变更通知(change notifications)的委托。
最后,IsDefault这个传统.NET属性实现了自己的访问器(accessors).这个访问器由GetValue和SetValue两个方法组成。两个方法都是System.Windows.DependencyObject类的实例方法。现在介绍一下DependencyObject类, DependencyObject是一个非常底层的基类,所有的DP类都继承自该类。它的GetValue方法会返回最后一次调用SetValue方法时所设置的值,如果SetValue始终没有被调用过,则会返回属性在注册时设置的默认值。实际上IsDefault属性(有事也叫做属性包装器)并不是必须的,对于Button的调用者来说,可以直接调用GetValue/SetValue方法。但是使用了.NET属性会在代码的读写上更加自然,并且可以应用到XAML中。
WARNINGXAML编译器在编译时会用到属性包装器,而在运行时WPF会直接调用底层的GetValue和SetValue方法。所以为了保持XAML代码与程序代码之间的一致,在属性包装器的GetValue和SetValue方法中绝对不能添加任何逻辑代码。如果您的确需要添加一些逻辑代码,可以写在变更通知的回调方法中。所有的WPF内置的属性包装器都遵循了这一原则。
表面上,List 3.3中的代码在实现简单的布尔值属性上过于冗长。但实际上,GetValue和SetValue内部使用了一种高效的存储,稀少的存储系统。并且 IsDefaultProperty是个静态成员(而不是实例成员),所以DP节省了相对于传统.NET属性的每一个实例的内存。如果所有的WPF控件都使用实例属性(像大多数的.NET属性那样),那么就会消耗掉数量可观的内存,因为本地的数据会附到每个实例上去:Button控件都有96个成员, Label控件则有89个,再算上其他等控件的成员,所消耗掉的内存会非常之多。幸好Button的96属性中有78个是DP,Label的89个属性中有71是DP。
实现DP的好处不仅在于内存方面,DP还将“属性的线程访问检查”,“所包含元素的重新呈现”等特性进行了集中化和标准化管理。例如,当一个属性改变时,要求元素UI刷新(比如改变Button控件的Background属性),只需要在注册属性时将 FrameworkPropertyMetadataOptions.AffectsRender传给 DependencyProperty.Register方法。而且DP还为我们带来另外3个特性(上文中提到过),我们会逐一查看这些特性。首先从变更通知(Change Notification)开始。
翻译小记:之前O翻译的文章最多1000字就打住了,从没有连续翻译像《WPF Unleash》过这么长的文章。呵呵,真把O累B了。 现在对那些大部头的作者/译者真是敬佩万分啊...
ps:发现自己真的没有排版的天分.....:(
就唠叨这么多吧,一会儿把下一篇贴上来
如果您有WPF方面的问题,可以给我留言,感谢大家浏览