在.NET中属性和事件是.NET抽象模型的核心部分,但是在WPF中改变了这些基础的任何一个。依赖项属性使用更高效的保存机制,并且支持附加的功能。依赖项属性还是WPF许多重要功能的基础,包括动画、数据绑定以及样式。尽管改变了这些基础,但是在代码中仍然可以使用与读取和设置传统的.NET属性相同的方式来读取和设置依赖项属性。
依赖项属性是专门针对WPF创建的。但是WPF库中的依赖项属性都适用普通的.NET属性过程进行了包装。从而可以通过常规的方式使用它们,这正是WPF能够改变基础组成部分(如属性),而不会扰乱.NET领域中其他部分的原因。下面让我们来学习如何定义、注册和使用依赖项属性。
- 定义依赖项属性—定义表示属性的对象,它是DependencyProperty类的一个实例。属性信息应该始终保持可用,甚至可能需要在多个类之间共享这些信息。因此必须将Dependency Property对象定义为与之相关连的类的静态字段。根据约定,定义依赖项属性的字段的名称为在普通属性的末尾加上单词"Property"。根据这种命名方式可以明确区分出以来属性的定义。如下FrameworkElement类的Margin属性示例:
public class FrameworkElement:UIElement { public static readonly DependencyProperty MarginProperty; }
在此使用了readonly,这意味着只能在FramworElement类的静态构造换书中对其进行设置。
- 为了能够使用依赖项属性,还需要使用WPF注册创建的依赖项属性。DependencyProperty对象不能被直接实例化,反而只能使用静态的DependencyProperty.Register()方法创建DependencyProperty实例。DependencyProperty对象在其创建之后不能被改变,因此所有DependencyProperty成员都只是只读的。它们的值必须作为Register()方法的参数提供。还以FrameworkElement类的Margin属性示例:
static FrameworkElement() { FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.AffectsMeasure); MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement), metadata, new ValidateValueCallback(FrameworkElement.IsMarginValid)); }
注册依赖项属性需要经历两个步骤。首先创建一个FrameworkPropertyMetadata对象,该对象指示希望通过依赖项属性使用什么服务。接下来通过调用DependencyProperty.Register()静态方法注册属性,需要提供如下属性:1、属性名(该示例是Margin)。2、属性使用的数据类型(该示例中为Thickness结构)。3、拥有该属性的类型(该示例中为FrameworkElement类)。4、一个具有附加属性设置的FrameworkPropertyMetadata对象,该要素是可选的。5、一个用于验证属性的毁掉函数,该要素是可选的。使用FrameworkPropertMetadata对象配置创建依赖项属性的附加功能,下面是所有FrameworkPropertMetadata类的属性。
- 添加属性包装器—创建依赖项属性的最后一步是使用传统.NET属性包装WPF依赖项属性。当创建属性包装器时,应当只包含对SetValue()方法和GetValue()方法的调用。
public Thickness Margin { set {SetValue(MarginProperty,value);} get { return (Thickness)GetValue(MarginProperty); } }
- WPF使用依赖项属性的方式—在WPF的许多功能都需要使用依赖项属性。但是,所有这些功能都是通过每个依赖项属性都支持的两个关键行为进行工作的—更改通知和动态值识别。当属性值发生变化时,依赖项属性不会自动引发事件,而是已通知一个属性值发生了变化。反而他们会触发一个受保护的名为OnpropertyChangedCallback()的方法。该方法通过两个WPF服务(数据绑定和触发器)传递信息,并调用PropertyChangedCallback回调函数(如果定义了该函数)。当属性发生变化时,如果希望进行响应,有两种选择:1、使用属性值创建一个绑定。(第8章)2、编写一个自动改变其他属性或者开始一个动画的触发器。(第11章)。依赖项属性没有提供一种通用饿方法以触发一些代码,从而对属性的变化进行响应。依赖项属性工作很重要的第二个功能是动态值识别。依赖项属性依赖于多个属性提供者,每个提供者都有其自己的优先级。当从属性检索值时,WPF复兴系统会通过一系列步骤获取最终的值。其优先级从低到高的顺序排列如下:默认值、继承而来的值、来自主题样式的值、来自项目样式的值、本地值。如上列表所示,可以通过直接应用一个值来覆盖整个层次。
- 共享的依赖项属性—一些类会共享同一个依赖项属性,尽管这些类具有不同的继承层次。例如:TextBlock.FonFamily属性和Control.FontFamily属性只想同一个静态的依赖项属性,该属性实际上是在TextElement类中定义的TextElement.FontFamilyProperty依赖项属性。TextElement类的静态构造函数注册该属性,而TextBlock类和Control类的静态构造函数知识简单的通过调用DependencyProperty.AddOwner()方法重用该属性:
TextBlock.FontFamilyProperty=TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));
- 附加的依赖项属性——在布局容器中可以看到附加属性最常见的例子。例如:Grid类定义的Row和Column属性,DockPanel类定义了Dock附加属性等。为了定义附加属性,需要使用RegisterAttached()方法,而不是使用Register()方法。下面是注册Grid.Row属性的例子:
FrameworkPropertyMetadata metadata=new FrameworkPropertMetadata(0,new PropertyChangedCallback(Grid.OnCellAttachedPropertyChanged)); Grid.RowProperty=DependencyProperty.RegisterAttached("Row",ypeof(int),typeof(Grid),metadata,new ValidateValueCallback(Grid.IsIntValueNotNegative));
附加属性不使用.NET属性包装器,反而附加属性需要调用两个静态方法来设置和获取属性值,这两个方法是用熟悉的SetValue()和GetValue()方法(继承自DependencyObject类),这两个方法应当命名为SetPropertyName()和GetPropertyName()。下面是实现Grid.Row附加属性的静态方法。
Public static int GetRow(UIElement element) { if(element==null) { throw new ArgumentNullException(...); } return (int)element.GetValue(Grid.RowProperty); } public static void SetRow(UIElement element,int value) { if(element == null) { throw new ArgumentNullException(...); } element.SetValue(Grid.RowProperty,value); }