依赖属性概述
Windows Presentation Foundation (WPF) 提供一组服务,这些服务可用于扩展类型的属性的功能。 这些服务通常统称为 WPF 属性系统。 由 WPF 属性系统支持的属性称为依赖属性。 本概述介绍 WPF 属性系统以及依赖属性的功能。 这包括如何在 XAML 和在代码中使用现有依赖属性。 本概述还介绍依赖属性所特有的方面(如依赖属性元数据),并说明如何在自定义类中创建自己的依赖属性。
先决条件
本主题假设你在 .NET 类型系统和面向对象的编程方面有一些基础知识。 为了能理解本主题中的示例,还应了解 XAML 并知道如何编写 WPF 应用程序。 有关详细信息,请参阅演练:我的第一个 WPF 桌面应用程序。
依赖属性和 CLR 属性
在 WPF 中,属性通常公开为标准 .NET 属性。 在基本级别,可以直接与这些属性交互,而不必了解它们是以依赖属性的形式实现的。 但是,应当熟悉 WPF 属性系统的部分或全部功能,以便利用这些功能。
依赖属性的用途在于提供一种方法来基于其他输入的值计算属性值。 这些其他输入可能包括系统属性(如主题和用户首选项)、实时属性确定机制(如数据绑定和动画/情节提要)、重用模板(如资源和样式)或者通过与元素树中其他元素的父子关系来公开的值。 另外,可以通过实现依赖属性来提供独立验证、默认值、监视其他属性的更改的回叫以及可以基于可能的运行时信息来强制指定属性值的系统。 派生类还可以通过重写依赖属性元数据(而不是重写现有属性的实际实现或者创建新属性)来更改现有属性的某些具体特征。
在 SDK 参考中,可以根据某个属性的托管引用页上是否存在“依赖属性信息”部分来确定该属性是否为依赖属性。 “依赖属性信息”部分包括一个指向该依赖属性的 DependencyProperty 标识符字段的链接,还包括一个为该属性设置的元数据选项列表、每个类的重写信息以及其他详细信息。
依赖属性支持 CLR 属性
依赖属性和 WPF 属性系统通过提供一个支持属性的类型来扩展属性功能,这是使用专用字段支持该属性的标准模式的替代实现方法。 此类型的名称为 DependencyProperty。 定义 WPF 属性系统的另一个重要类型是 DependencyObject。 DependencyObject 定义可以注册和拥有依赖属性的基类。
下面列出了与依赖属性一起使用的术语:
依赖属性:DependencyProperty 支持的属性。
依赖属性标识符:一个 DependencyProperty 实例,在注册依赖属性时以返回值形式获取它,之后将其存储为类的静态成员。 对于与 WPF 属性系统交互的许多 API,此标识符用作一个参数。
CLR“包装器”:属性的实际 get 和 set 实现。 这些实现通过在 GetValue 和 SetValue 调用中使用依赖属性标识符来并入依赖属性标识符,从而使用 WPF 属性系统为属性提供支持。
下面的示例定义 IsSpinning
依赖属性,并说明 DependencyProperty 标识符与它所支持的属性之间的关系。
public static readonly DependencyProperty IsSpinningProperty =
DependencyProperty.Register(
"IsSpinning", typeof(Boolean),
typeof(MyCode)
);
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
Public Shared ReadOnly IsSpinningProperty As DependencyProperty =
DependencyProperty.Register("IsSpinning",
GetType(Boolean),
GetType(MyCode))
Public Property IsSpinning() As Boolean
Get
Return CBool(GetValue(IsSpinningProperty))
End Get
Set(ByVal value As Boolean)
SetValue(IsSpinningProperty, value)
End Set
End Property
属性及其支持性 DependencyProperty 字段的命名约定非常重要。 字段总是与属性同名,但其后面追加了 Property
后缀。 有关此约定及其原因的详细信息,请参阅自定义依赖属性。
设置属性值
可以在代码或 XAML 中设置属性。
在 XAML 中设置属性值
下面的 XAML 示例将按钮的背景色指定为红色。 此示例演示这样一种情况:在所生成的代码中,WPF XAML 分析器将 XAML 属性的简单字符串值类型转换为 WPF 类型(一个 Color,通过 SolidColorBrush 实现)。
<Button Background="Red" Content="Button!"/>
XAML 支持多种设置属性的语法格式。 要对特定的属性使用哪种语法取决于该属性所使用的值类型以及其他因素(例如,是否存在类型转换器)。 有关属性设置的 XAML 语法的详细信息,请参阅 WPF 中的 XAML 和 XAML 语法详述。
作为非属性语法的示例,下面的 XAML 示例显示了另一种按钮背景。 这一次不是设置简单的纯色,而是将背景设置为图像,用一个元素表示该图像并将该图像的源指定为嵌套元素的属性。 这是属性元素语法的示例。
<Button Content="Button!">
<Button.Background>
<ImageBrush ImageSource="wavy.jpg"/>
</Button.Background>
</Button>
在代码中设置属性
在代码中设置依赖属性值通常只是调用由 CLR“包装器”公开的 set 实现。
Button myButton = new Button();
myButton.Width = 200.0;
Dim myButton As New Button()
myButton.Width = 200.0
获取属性值实质上也是在调用 get“包装器”实现:
double whatWidth;
whatWidth = myButton.Width;
Dim whatWidth As Double
whatWidth = myButton.Width
还可以直接调用属性系统 API GetValue 和 SetValue。 如果使用的是现有属性,则上述操作通常不是必需的(使用包装器会更方便,并能够更好地向开发人员工具公开属性),但是在某些情况下适合直接调用 API。
还可以在 XAML 中设置属性,然后通过代码隐藏在代码中访问这些属性。 有关详细信息,请参阅 WPF 中的代码隐藏和 XAML。
由依赖属性提供的属性功能
依赖属性提供用来扩展属性功能的功能,这与字段支持的属性相反。 通常,此类功能代表或支持以下特定功能之一:
资源
依赖属性值可以通过引用资源来设置。 资源通常指定为页面根元素或应用程序的 Resources
属性值(通过这些位置可以非常方便地访问资源)。 以下示例演示如何定义 SolidColorBrush 资源。
<DockPanel.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</DockPanel.Resources>
在定义该资源之后,可以引用该资源并使用它来提供属性值:
<Button Background="{DynamicResource MyBrush}" Content="I am gold" />
这个特定资源称为 DynamicResource 标记扩展(在 WPF XAML 中,可以使用静态或动态资源引用)。 若要使用动态资源引用,必须设置为依赖属性,因此它专门是由 WPF 属性系统启用的动态资源引用用法。 有关详细信息,请参阅 XAML 资源。
注意
资源被视为本地值,这意味着,如果设置另一个本地值,该资源引用将被消除。 有关详细信息,请参阅依赖属性值优先级。
数据绑定
依赖属性可以通过数据绑定来引用值。 数据绑定通过特定标记扩展语法(在 XAML 中)或 Binding 对象(在代码中)起作用。 使用数据绑定,最终属性值的确定将延迟到运行时,在运行时,将从数据源获取属性值。
以下示例使用在 XAML 中声明的绑定来设置 Button 的 Content 属性。 该绑定使用继承的数据上下文和 XmlDataProvider 数据源(未显示)。 绑定本身通过数据源中的 XPath 指定所需的源属性。
<Button Content="{Binding XPath=Team/@TeamName}"/>
注意
绑定被视为本地值,这意味着,如果设置另一个本地值,该绑定将被消除。 有关详细信息,请参阅依赖属性值优先级。
为了生成数据绑定操作的 DependencyObject 源属性值的更改通知,依赖属性或 DependencyObject 类本身不支持 INotifyPropertyChanged。 有关如何创建要用在数据绑定中并且可以向数据绑定目标报告变化的属性的详细信息,请参阅数据绑定概述。
样式
样式和模板是使用依赖属性的两个主要激发方案。 在设置定义应用程序用户界面 (UI) 的属性时,样式尤其有用。 在 XAML 中,通常将样式定义为资源。 样式与属性系统交互,因为它们通常包含特定属性的“资源库”,以及基于另一个属性的实时值更改属性值的“触发器”。
以下示例创建一个简单的样式(该样式在 Resources 字典中定义,未显示),然后将该样式直接应用于 Button 的 Style 属性。 样式中的资源库将带样式 Button 的 Background 属性设置为绿色。
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>
<Button Style="{StaticResource GreenButtonStyle}">I am green!</Button>
有关详细信息,请参阅样式设置和模板化。
动画
可以对依赖属性进行动画处理。 在应用和运行动画时,经过动画处理的值的操作优先级高于该属性以其他方式具有的任何值(如本地值)。
以下示例在 Button 属性上对 Background 进行动画处理(从技术上讲,通过使用属性元素语法将空白 SolidColorBrush 指定为 Background 来对 Background 进行动画处理,然后该 SolidColorBrush 的 Color 属性就是直接动画处理过的属性)。
<Button>I am animated
<Button.Background>
<SolidColorBrush x:Name="AnimBrush"/>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimBrush"
Storyboard.TargetProperty="(SolidColorBrush.Color)"
From="Red" To="Green" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
有关对属性进行动画处理的详细信息,请参阅动画概述和情节提要概述。
元数据重写
在从最初注册依赖属性的类派生时,可以通过重写依赖属性的元数据来更改该属性的某些行为。 重写元数据依赖于 DependencyProperty 标识符。 重写元数据不需要重新实现属性。 元数据的更改由属性系统在本机处理;对于所有从基类继承的属性,每个类都有可能基于每个类型保留元数据。
以下示例将替代 DefaultStyleKey 依赖属性的元数据。 重写此特定依赖属性的元数据是某个实现模式的一部分,该模式创建可以使用主题中的默认样式的控件。
public class SpinnerControl : ItemsControl
{
static SpinnerControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(SpinnerControl),
new FrameworkPropertyMetadata(typeof(SpinnerControl))
);
}
}
Public Class SpinnerControl
Inherits ItemsControl
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(SpinnerControl), New FrameworkPropertyMetadata(GetType(SpinnerControl)))
End Sub
End Class
有关替代或获取属性元数据的详细信息,请参阅依赖属性元数据。
属性值继承
元素可以从其在对象树中的父级继承依赖属性的值。
注意
属性值继承行为并未针对所有依赖属性在全局启用,因为继承的计算时间确实会对性能产生一定的影响。 属性值继承通常只有在特定方案指出适合使用属性值继承时才对属性启用。 可以通过在 SDK 参考中查看某个依赖属性的依赖属性信息部分,来确定该依赖属性是否继承属性值。
下面的示例演示一个绑定,并设置指定绑定(在前面的绑定示例中未显示出来)的源的 DataContext 属性。 子对象中的任何后续绑定都无需指定源,它们可以使用父对象 StackPanel 中 DataContext 的继承值。 (或者,子对象可以选择直接在 Binding 中指定自己的 DataContext 或 Source,并且有意不将继承值用于其绑定的数据上下文。)
<StackPanel Canvas.Top="50" DataContext="{Binding Source={StaticResource XmlTeamsSource}}">
<Button Content="{Binding XPath=Team/@TeamName}"/>
</StackPanel>
有关详细信息,请参阅属性值继承。
WPF 设计器集成
如果自定义控件具有实现为依赖属性的属性,则它会收到相应的适用于 Visual Studio 的 WPF 设计器支持。 一个示例就是能够在“属性”窗口中编辑直接依赖属性和附加依赖属性。 有关详细信息,请参阅控件创作概述。
依赖项属性值优先级
获取依赖属性的值时,获得的值可能是通过参与 WPF 属性系统的其他任一基于属性的输入而在该属性上设置的。 由于存在依赖属性值优先级,使得属性获取值的方式的各种方案得以按可预测的方式交互。
请看下面的示例。 该示例包含适用于所有按钮及其 Background 属性的样式,但也会指定一个具有本地设置的 Background 值的按钮。
注意
SDK 文档在讨论依赖属性时有时会使用“本地值”或“本地设置的值”等术语。 本地设置的值是指在代码中直接为对象实例设置的属性值,或者在 XAML 中设置为元素特性的属性值。
原则上,对于第一个按钮,该属性会设置两次,但是仅应用一个值,即具有最高优先级的值。 本地设置的值具有最高优先级(对于正在运行的动画除外,但是在本示例中没有应用动画),因此,对于第一个按钮的背景将使用本地设置的值,而不使用样式资源库值。 第二个按钮没有本地值(而且没有其他比样式资源库优先级更高的值),因此该按钮中的背景来自样式资源库。
<StackPanel>
<StackPanel.Resources>
<Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red"/>
</Style>
</StackPanel.Resources>
<Button Background="Green">I am NOT red!</Button>
<Button>I am styled red</Button>
</StackPanel>
为什么存在依赖属性优先级?
通常,你不会希望总是应用样式,而且不希望样式遮盖单个元素的哪怕一个本地设置值(否则,通常难以使用样式或元素)。 因此,来自样式的值的操作优先级低于本地设置的值。 有关依赖属性以及它的有效值可能来自何处的更完整列表,请参阅依赖属性值优先级。
注意
在 WPF 元素定义了许多非依赖属性的属性。 一般说来,只有在需要支持至少一个由属性系统启用的方案(数据绑定、样式、动画、默认值支持、继承、附加属性或失效)时,才将属性实现为依赖属性。
了解有关依赖属性的详细信息
附加属性是一种支持 XAML 中的专用语法的属性。 附加属性通常与公共语言运行时属性 (CLR) 没有 1:1 的对应关系,而且不一定是依赖属性。 附加属性的典型用途是使子元素可以向其父元素报告属性值,即使父元素和子元素的类成员列表中均没有该属性也是如此。 一种主要情况是使子元素能够告知父元素应如何在 UI 中呈现它们;有关示例,请参阅 Dock 或 Left。 有关详细信息,请参阅附加属性概述。
组件开发人员或应用程序开发人员可能希望创建自己的依赖属性,以便实现数据绑定或样式支持之类的功能,或者实现对失效和值强制的支持。 有关详细信息,请参阅自定义依赖属性。
依赖属性应当被视为公共属性,这些公共属性可以由任何具有实例访问权限的调用方访问,或至少可被这样的调用方发现。 有关详细信息,请参阅依赖属性的安全性。