DP 与 DO(学习)
WPF引入了一种新的属性:Dependency属性。Dependency属性的应用贯串在整个WPF当中。Dependency属性根据多个提供对象来决定它的值。并且是及时更新的。提供对象可以是动画,不断地改变它的值。也可以是父元素,它的属性值被继承到子元素。毫无疑问,Dependency属性最大的特点就是内建的变化通知功能。提供Dependency属性功能主要是为了直接从声明标记提供丰富的功能。WPF声明的友好设计的关键是大量的使用属性。如果没有Dependency属性,我们将不得不编写大量的代码。
关于WPF的Dependency属性,重点有三个方面:
1、变化通知功能:属性的值被改变后,通知界面进行更新。
2、属性值的继承功能:子元素将继承父元素中对应属性名的值。
3、支持多个提供对象:我们可以通过多种方式来设置Dependency属性的值。
先看如何实现一个标准的Dependency属性。
1 public class Button : ButtonBase 2 3 { 4 5 // The dependency property 6 7 public static readonly DependencyProperty IsDefaultProperty; 8 9 static Button() 10 11 { 12 13 // Register the property 14 15 Button.IsDefaultProperty = DependencyProperty.Register(“IsDefault”, typeof(bool), typeof(Button), 16 17 new FrameworkPropertyMetadata(false, 18 19 new PropertyChangedCallback(OnIsDefaultChanged))); 20 21 … 22 23 } 24 25 // A .NET property wrapper (optional) 26 27 public bool IsDefault 28 29 { 30 31 get { return (bool)GetValue(Button.IsDefaultProperty); } 32 33 set { SetValue(Button.IsDefaultProperty, value); } 34 35 } 36 37 // A property changed callback (optional) 38 39 private static void OnIsDefaultChanged( 40 41 DependencyObject o, DependencyPropertyChangedEventArgs e) { … } 42 43 … 44 45 }
在上面的实现代码中,System.Windows.DependencyProperty类表示的静态字段IsDefaultProperty才是真正的Dependency属性。为了方便,所有的Dependency属性都是公有、静态的,并且还有属性后缀。通常创建Dependency属性可用静态方法DependencyProperty.Register。参数的属性名称、类型、使用这个属性的类。并且可以根据重载的方法提供其他的通知事件处理和默认值等等。这些相关的信息可参考FrameworkPropertyMetadata类的多个重载构造函数。
最后,实现了一个.NET属性,其中调用了从System.Windows.DependencyObject继承的GetValue、 SetValue方法。所有具有Dependency属性的类都肯定会继承这个类。GetValue方法返回最后一次设置的属性值,如果还没有调用一次 SetValue,返回的将是Register方法所注册的默认值。而且,这种.NET样式的属性封装是可选的,因为GetValue/SetValue 方法本身是公有的。我们可以直接调用这两个函数,但是这样的封装使代码更可读。
虽然这种实现方式比较麻烦,但是,由于GetValue/SetValue方法使用了一种高效的小型存储系统,以及真正的Dependency属性是静态字段(不是实例字段),Dependency属性的这种实现可以大大的减少每个属性实例的存储空间。想象一下,如果Button有50个属性,并且全部是非静态的实例字段,那么每个Button实例都含有这样50个属性的空间,这就存在很大的空间浪费。除了节省空间,Dependency属性的实现还集中、并且标准化了属性的线程访问检查、提示元素重新提交等等。
在前一个Post中,曾提到将要重点研究Dependency属性的三个方面:变化通知;属性值的继承;支持多个提供对象。下面,我将分别就这三个内容进行简单地说明。
【变化通知】
在任何时候,只要Dependency属性的值发生了变化,WPF可以自动地根据属性的元数据触发不同的行为。前面提到过:Dependency属性最大的特点就是内建的变化通知功能。这种内建变化通知所提供的最值得注意的就是属性触发器(Property Trigger),就是它使用我们不需要编写任何的程序代码就能在属性变化使执行自定义行为。请看下面XAML编码的一个属性触发器例子:
<Trigger Property=”IsMouseOver” Value=”True”> <Setter Property=”Foreground” Value=”Blue”/> </Trigger>
使用属性触发器时需要注意:触发器默认适用于每个类对象。而且,在WPF 3.0中由于人为的限制,Property Trigger将不能应用在单独的元素。只能应用在某个Style对象之中。因此,如果想在某个单独对象上实现Property Trigger。必须用如下的XAML进行封装:
1 <Button MinWidth=”75” Margin=”10”> 2 3 <Button.Style> 4 5 <Style TargetType=”{x:Type Button}”> 6 7 <Style.Triggers> 8 9 <Trigger Property=”IsMouseOver” Value=”True”> 10 11 <Setter Property=”Foreground” Value=”Blue”/> 12 13 </Trigger> 14 15 </Style.Triggers> 16 17 </Style> 18 19 </Button.Style> 20 21 OK 22 23 </Button>
【属性值继承】
属性值继承是指在设置逻辑树某个结点元素的属性后,它的所有之结点都继承这个属性值(当然,前提是子元素必须支持这个属性)。我们仍然利用闲话WPF之八中的一个例子进行说明:
<Window FontSize=”30”> <StackPanel> <Label>LabelText</Lable> </StackPanel> </Window>
我们修改了Window是FontSize属性为30。通过实际观察将发现它的子元素Label的FontSize也变为了30。注意这里的StackPanel是一个容器元素,它本身并不支持FontSize属性。
现在我们给上面的Window添加一个状态栏。XAML代码如下:
<Window ......> <StackPanel> <Label>LabelText</Lable> <StatusBar>This is a Statusbar</StatusBar> </StackPanel> </Window>
这时你会发现:虽然StatusBar支持这个FontSize这个属性,它也是Window的子元素,但是它的字体大小却没有变化。为什么呢?因为并不是所有的元素都支持属性值继承。还存在如下两种例外的情况:
1、部分Dependency属性在用Register注册时可以指定Inherits为不可继承。
2、如果有其他更高优先级方法设置了其他的值。(关于优先级的介绍且看下面分解。)
部分控件如StatusBar、Menu和Tooptip内部设置它们的字体属性值以匹配当前系统的设置。这样用户通过控制面板可以修改它们的外观。这种方法存在一个问题:StatusBar等截获了从父元素继承来的属性,并且不影响其子元素。比如,如果我们在StatusBar中添加了一个Button。这个Button的字体属性会因为StatusBar的截断没没有改变,将保留其默认值。
附加说明:属性值继承的最初设计只适用于元素Tree,现在已经进行多个方面的扩展。比如,值可以传递下级看起来像Children,但在逻辑或者视觉Tree中并不是Children的某些元素。这些伪装的子元素可以是触发器、属性的值,只要它是从Freezable继承的对象。【支持多个提供对象】
WPT提供了独立的、强大的机制来设置Dependency属性的值。由于支持多个提供对象,如果没有很好的机制来处理这些提供对象,那么 Dependency属性的值将是不可预测的、系统也将变得混乱。Dependency属性的值取决于这些提供对象,它们以一定的顺序和优先级排列。
下图说明了WPF在计算Dependency属性最终值的5个步骤:
基本的计算过程是:
确定基础值====>计算值(如果是表达式)===>应用动画====>强制值===>值验证
1、确定基础值
多数的提供对象都会影响基础值,下面以优先级顺序列出了可以设置多数Dependency属性值的八个提供对象:
1、Local Value 2、Style Triggers 3、Template Triggers 4、Style Setters
5、Theme Style Triggers 6、Theme Style Setters 7、Property Value Inheritance 8、Default Value
Local Value技术上表示任何对DependencyObject.SetValue的调用。它的最常见形式就是在XAML或者代码中的属性赋值。因为我们通常用.NET的属性方式封装了对GetValue/SetValue的调用。Regitser注册时指定的默认值位于处理过程的最后一步。关于其它的提供对象,如Style、Template将在以后介绍,敬请关注后续内容。
2、计算值
如果第一步得到的是一个表达式,WPF将计算表达式以得到一个具体的值。在3.0版本的WPF中,只有动态资源或者是数据绑定才可能有表达式。也许将来版本的WPF会支持其它类型的表达式。
3、应用动画
如果当前有一个或者多个动画在运行,它们具有修改当前属性值、或者完全替代它的能力。因此,动画的级别比其它属性提供对象都高,甚至是Local Value,我们必须记住这一点。
4、强制值
在处理完所有的提供对象后,WPF将最终的属性值传递到CoerceValueCallback委派。如果Dependency属性在注册时提供了 这样的委派,那么就应该根据自定义逻辑返回一个新的值。比如ProgressBar,当所有提供对象最后所提供的值超出了其定义的最大、最小值范围 时,ProgressBar将利用这个CoerceValueCallback委派限制在这个范围之内。
5、值验证
最后,前缀的强制值将传递给ValidateValueCallback委派,如果Dependency属性注册了这个委派。当值有效时委派必须返回True,否则返回False。返回False将抛出异常,终止整个进程。
附加说明:如果我们不知道给定的Dependency属性的值来源于何处,可以调用静态的 DependencyPropertyHelper.GetValueSource方法。它作为调试时的辅助工具,有时能给我们提供帮助。方法会返回一个 ValueSource结构。ValueSource结构中的属性成员BaseValueSource、IsExpression、 IsAnimated、IsCoerced分别表示了前面列出的八个提供对象的相应类型。注意:请不要在最后的发布产品中使用这个方法,因为在将来版本的 WPF中可能有不同的行为。只应该将其作为调试工具。