Windows Presentation Foundation (WPF) 应用程序开发人员和组件作者可能希望创建自定义的依赖项属性的原因,以及介绍可以提高该属性的性能、可用性或通用性的实现步骤和某些实现选项,依赖属性的作用.
在 WPF 类上实现的依赖项属性示例包括 Background 属性、Width 属性和 Text 属性等。类公开的每个依赖项属性都有一个对应的、在同一个类上公开的 DependencyProperty 类型的公共静态字段。这是依赖项属性的标识符。该标识符的命名约定为:在依赖项属性的名称后面追加字符串 Property。例如,与 Background 属性对应的 DependencyProperty 标识符字段为 BackgroundProperty。该标识符用于在注册依赖项属性时存储其相关信息,并且在随后可用于与依赖项属性有关的其他操作,如调用 SetValue。
如依赖项属性概述中所述,由于“包装”实现,WPF 中的所有依赖项属性(大多数附加属性除外)也是 CLR 属性。因此,可以从代码中获取或设置依赖项属性,方法是按照与使用其他 CLR 属性相同的方式来调用定义包装的 CLR 访问器。作为确立的依赖项属性的使用者,您通常不会使用 DependencyObject 方法 GetValue 和 SetValue,这两种方法是基础属性系统的连接点。相反,CLR 属性的现有实现会已使用相应的标识符字段在该属性的 get 和 set 包装实现内调用 GetValue 和 SetValue。如果要亲自实现自定义的依赖项属性,则需要用类似的方法定义包装。
-
(可选)为依赖项属性创建属性元数据。
-
在属性系统中注册属性名称,并指定所有者类型和属性值的类型。如果使用了属性元数据,则也应同时指定。
-
在所有者类型上将 DependencyProperty 标识符定义为 public static readonly 字段。
-
定义其名称与依赖项属性的名称匹配的 CLR“包装”属性。实现 CLR“包装”属性的 get 和 set 访问器,以便与支持此属性的依赖项属性进行连接。
在属性系统中注册属性
为将属性设置为依赖项属性,必须在属性系统维护的表中注册该属性,并为其指定一个唯一的标识符,以用作属性系统后续操作的限定符。这些操作可能是内部操作,也可能是您自己的代码调用属性系统 API。若要注册属性,请在类体中调用 Register 方法(在类的内部,但在任何成员定义的外部)。Register 方法调用还提供了标识符字段以作为返回值。在其他成员定义外部执行 Register 调用的原因是:使用此返回值可以分配和创建一个类型为 DependencyProperty 的 public static readonly 字段,以作为类的一部分。此字段将成为依赖项属性的标识符。
您必须完全遵循为依赖项属性建立的有关命名约定,但例外情况除外。
如此例中所示,依赖项属性本身将具有一个基本名称“AquariumGraphic”,该名称是作为 Register 的第一个参数提供的。该名称在每个注册类型中必须唯一。通过基类型继承的依赖项属性将被视为已是注册类型的一部分;无法再次注册已继承属性的名称。但是,即使不继承该依赖项属性,也可以使用一种方法来添加作为依赖项属性所有者的类;
创建标识符字段时,请将此字段命名为所注册的属性名称,并加上后缀 Property。此字段是依赖项属性的标识符,随后将用作包装中进行的 SetValue 和 GetValue 调用的输入,供任何其他代码通过您自己的代码、允许的任何外部代码访问、属性系统以及可能通过 XAML 处理器来访问属性。
实现“包装”
包装实现应调用 get 实现中的 GetValue 和 set 实现中的 SetValue。(为清楚起见,此处还显示了原始注册调用和字段。)
除例外情况以外,包装实现在其他情况中应只分别执行 GetValue 和 SetValue 操作。
WPF 类中提供的所有现有公共依赖项属性都使用这一简单的包装实现模型;依赖项属性如何工作的大部分复杂性或者在本质上是一种属性系统行为,或者是通过其他概念(如通过属性元数据进行强制回调或属性更改回调)实现的。
此外,按照约定,包装属性的名称必须与所选择并指定为注册该属性的 Register 调用的第一个参数的名称相同。如果属性不遵循该约定,尽管不一定要禁用所有可能的用法,但您将会遇到几个突出的问题:
-
某些方面的样式和模板将不起作用。
-
许多工具和设计器必须依赖于命名约定,才能正确序列化 XAML,或按每个属性级别提供设计器环境帮助。
-
WPF XAML 加载程序的当前实现会完全跳过包装,并且在处理特性值时依赖于命名约定。
新依赖项属性的属性元数据
注册依赖项属性时,通过属性系统进行的注册将会创建元数据对象以存储属性特性。如果属性是使用 Register 的简单签名注册的,则会设置其中许多特性的默认值。通过 Register 的其他签名,可以在注册属性时指定所需的元数据。为依赖项属性提供的最常见元数据是为它们提供一个默认值,该默认值适用于使用该属性的新实例。
如果要创建在 FrameworkElement 的派生类上存在的依赖项属性,则可以使用更专用的元数据类 FrameworkPropertyMetadata,而不是 PropertyMetadata 基类。FrameworkPropertyMetadata 类的构造函数具有几种签名,您可以在其中组合指定多个元数据特性。如果要仅指定默认值,请使用接受类型为 Object 的单个参数的签名,并将该对象参数作为属性特定于类型的默认值进行传递。(提供的默认值必须是 Register 调用中作为 propertyType 参数提供的类型。)
对于 FrameworkPropertyMetadata,还可以为属性指定元数据选项标志。在注册之后,这些标志将转换为属性元数据中的不同属性,用于将某些条件传送给其他进程(如布局引擎)。
设置相应的元数据标志
-
如果属性(或属性值更改)影响用户界面 (UI),尤其是影响布局系统在页面中调整元素大小或呈现元素的方式,请设置以下一个或多个标志:AffectsMeasure、AffectsArrange 和 AffectsRender。
-
AffectsMeasure 指示在对此属性进行更改时,需要更改包含对象在父对象中可能需要更多或较少空间的 UI 呈现。例如,“Width”属性应设置此标志。
-
AffectsArrange 指示在对此属性进行更改时,需要更改通常无须更改专用空间、但又指示该空间中的位置已发生了更改的 UI 呈现。例如,“Alignment”属性应设置此标志。
-
AffectsRender 指示已发生了某些不会影响布局和度量,但确实需要其他呈现的其他更改。更改现有元素的颜色的属性便是一个示例,如“Background”。
-
这些标志在元数据中通常用作协议,以便您自己重写实现属性系统或布局回调。例如,如果实例的任何属性报告值发生更改,并且在其元数据中将 AffectsArrange 设置为 true,则您可能具有将调用 InvalidateArrange 的 OnPropertyChanged 回调。
-
看了这么多你应该了解什么是依赖属性,下面通过实例彻底展示依赖属性:
xaml文件:
<Window x:Class="WPF依赖属性简单演示.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Background="Tomato"> <Window.Resources> <SolidColorBrush x:Key="MyBrush" Color="Azure"/> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="72*" /> <ColumnDefinition Width="206*" /> </Grid.ColumnDefinitions> <!--直接从资源中获取属性,可以静态或者动态的获取--> <Button Background= "{StaticResource MyBrush}" Content="1 使用依赖属性从资源动态获取参数" Height="47" VerticalAlignment="Top" Margin="37,38,31,0" Grid.ColumnSpan="2" Click="Button_Click" /> <Button Margin="37,0,31,172" Name="button1" Click="button1_Click" Grid.ColumnSpan="2" Height="48" VerticalAlignment="Bottom">2 Get 依赖属性</Button> <TextBox Margin="37,0,31,36" Name="textBox1" Height="41" VerticalAlignment="Bottom" Grid.ColumnSpan="2">你好!</TextBox> <Button Margin="37,0,31,131" Name="button2" Click="button2_Click" Grid.ColumnSpan="2" Height="35" VerticalAlignment="Bottom">3 Set 依赖属性</Button> <Button Height="42" Margin="37,0,31,83" Name="button3" VerticalAlignment="Bottom" Click="button3_Click" Grid.ColumnSpan="2">4 使用属性封装依赖属性</Button> </Grid> </Window>
后台代码详解:
//属性系统中注册属性 private static readonly DependencyProperty myNameProperty = DependencyProperty.Register( "myName",//指定注册名 typeof(string),//变量类型 typeof(MainWindow),//从MainWindow获取 new FrameworkPropertyMetadata("这是元数据默认展示值",//指定元数据 FrameworkPropertyMetadataOptions.AffectsArrange,//设置相应的元数据标志,不同方式会产生不同性能问题 new PropertyChangedCallback(OnMyNamePropertyChanged)//回调当值发生变更时就发生回调 ) ); //进行属性封装“包装” public string MyNameProperty { get { return this.GetValue(MainWindow.myNameProperty).ToString(); } set { SetValue(MainWindow.myNameProperty, value); } } public static void OnMyNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { global::System.Windows.MessageBox.Show("值已经发生变动"); } private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("依赖属性直接从资源动态获取参数"); } private void button1_Click(object sender, RoutedEventArgs e) { global::System.Windows.MessageBox.Show(this.GetValue(MainWindow.myNameProperty).ToString()); } private void button2_Click(object sender, RoutedEventArgs e) { //依赖属性类型MainWindow.myNameProperty,vlue值 this.SetValue(MainWindow.myNameProperty, this.textBox1.Text); } private void button3_Click(object sender, RoutedEventArgs e) { global::System.Windows.MessageBox.Show(this.MyNameProperty); }
效果展示: