WPF中引入了一种新的属性——Dependency Property,这一属性的应用贯穿于整个WPF中,用来实现Style、绑定、动画等。之所以称其为Dependency Property,是因为它们依赖一些其他的property和外在的影响,在任何时刻都是依靠多个提供程序来决定它的值的。这些提供程序可以是从父元素中进行的属性值继承(不要和类之间的继承关系混淆了),或者是一段一直在改变的动画等。
以FontSize为例,在传统的.NET编程中,对象的font size会被存放在此类的private字段中,或许会用下面的默认值进行初始化:
private double fntsize = 11;
此private成员变量应该会以public的FontSize property出现:
public double FontSize
{
get
{
return fntsize;
}
set
{
fntsize = value;
...
}
}
在set 访问器中出现的“…”指的是:这里需要更多的代码。或许此控件需要改变其尺寸,或者至少需要被重绘,或许FontSizeChanged事件会发生,或许需要为此FontSize的继承,一一列举元素tree的后代等。
这一切,在WPF的Dependency Property中就没那么麻烦了,一切都是“自动”的,不需要写额外的代码。因此,Dependency Property的最大特征是其内建的变化通知的能力。提供这样的能力给属性,其动力在于能够在声明标记中直接启用富功能。WPF友好声明设计的关键在于它使用了很多属性。例如,Button控件有96个公共属性,属性可以方便地在XAML设置而不用写后台代码。但是如果Dependency Property没有额外的垂直传递,在不写额外代码的情况下,很难在设置属性这样简单的动作中获得想要的结果。
在本文中,我们将要简单得看一下Dependency Property的实现,让讨论更加具体。然后我们再深入分析以下三个方面:
1) 变化通知能力
2) 属性值继承
3) 支持多个提供程序
一个标准的依赖属性实现(下面的代码可能接近FontSizeProperty如何在Control类中实现):
public class Control : FrameworkElement
{
public static readonly DependencyProperty FontSizeProperty;
static Control()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = 11;
metadata.AffectsMeasure = true;
metadata.Inherits = true;
metadata.IsNotDataBindable = false;
metadata.DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
metadata.PropertyChangedCallback = new PropertyChangedCallback(OnFontSizeChanged);
FontSizeProperty = DependencyProperty.Register("FontSize", typeof(double), typeof(Control),
metadata, new ValidateValueCallback(ValidateFontSize));
}
// .NET 属性包装器(可选)
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
// 属性改变的回调(可选)
static void OnFontSizeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
...
}
// 属性值验证的回调(可选)
static bool ValidateFontSize(object obj)
{
double dFontSize = (double)obj;
return dFontSize > 0 && dFontSize <= 35791;
}
}
FontSizeProperty被称为Dependency Property,它是public且static,这意味着此成员变量和类有关联,而非和对象有关联。静态的只读成员只能够在“类成员变量定义本身”或“静态构造函数”中被设定。一般来说,类是通过调用静态的Dependency.Register方法,该方法需要一个名称(FontSize)、一个属性类型(double)以及拥有这个属性的类(Control)。通过不同的Register方法重载,你可以传入metadata(元数据)来告诉WPF该属性的默认值、如何处理该属性、如何处理属性值改变的回调、如何处理强制值转换以及如何验证值等。
最后,那个叫做FontSize的属性会调用继承自System.Windows.DependencyObject的GetValue和SetValue方法来实现自己的访问器。System.Windows.DependencyObject是底层基类,这是拥有Dependency Property的类必须继承的。GetValue返回最后一个由SetValue设置的值,如果SetValue从未调用过,那么就是该属性注册时的默认值。FontSize属性包装器并不是必需的,Control的使用者可能会直接调用GetValue和SetValue方法,因为它们是公开的。以.NET属性包装器的方式实现会让读写属性显得自然,并且还允许通过XAML设置属性。
注意:在运行时,XAML绕过了.NET属性包装器直接设置依赖属性
虽然XAML编译器在编译时是依靠该属性包装器的,但在运行时WPF是直接调用GetValue和SetValue的!因此,为了让使用XAML设置属性与使用过程式代码设置属性保持一致,在属性包装器中除了GetValue和SetValue调用以外,不应该包含任何其他逻辑,这是至关重要的。如果需要添加自定义逻辑,应该在注册的回调函数中添加。
即便传递到GetValue和SetValue方法内当参数的Dependency Property对象是静态的,GetValue和SetValue却是实例方法,它们值的设定和获取都和特定的实例有关。而且,GetValue和SetValue内部使用了高效的稀疏存储系统,与传统的.NET属性相比,Dependency Property的实现节省了保存每个实例所需要的内存。
Dependency Property的好处远不止内存使用这一项。它把用来检查线程访问、请求容器元素重新呈现、变化通知等相当一部分的代码集中起来,并作标准化处理,而这部分代码原本是要由属性实现者自己来写的。
关于要深入分析的三部分,将在其他的Post中进行说明。
A Za A Za Fighting!