依赖属性概述 (WPF .NET)

Windows Presentation Foundation (WPF) 提供一组服务,这些服务可用于扩展类型的属性的功能。 这些服务统称为 WPF 属性系统。 由 WPF 属性系统提供支持的属性称为依赖属性。 本概述文章介绍 WPF 属性系统和依赖属性的功能,包括如何在 XAML 和代码中使用现有的依赖属性。 本概述还介绍依赖属性所特有的方面(如依赖属性元数据),并说明如何在自定义类中创建自己的依赖属性。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

先决条件

本文假定你对 .NET 类型系统和面向对象的编程有基本的了解。 若要理解本文中的示例,了解 XAML 并知道如何编写 WPF 应用程序很有帮助。 有关详细信息,请参阅教程:使用 .NET 创建新的 WPF 应用

依赖属性和 CLR 属性

WPF 属性通常公开为标准 .NET 属性。 你可能在基本级别与这些属性进行交互,而不必了解它们是以依赖属性的形式实现的。 但是,熟悉 WPF 属性系统的部分或全部功能有助于充分利用这些功能。

依赖属性的用途在于提供一种方法来基于其他输入的值计算属性值,例如:

  • 系统属性,例如主题和用户首选项。
  • 即时属性确定机制,例如数据绑定和动画/情节提要。
  • 多用途模板,例如资源和样式。
  • 通过与元素树中其他元素的父子关系知道的值。

此外,依赖属性还可以提供:

  • 独立验证。
  • 默认值。
  • 回调,用于监视对其他属性的更改。
  • 可以根据运行时信息强制转换属性值的系统。

派生类可以通过替代依赖属性的元数据(而不是替代现有属性的实际实现或创建新属性)来更改现有属性的某些特征。

在 SDK 参考中,可以根据某个属性的托管引用页上是否有“依赖属性信息”部分来确定该属性是否为依赖属性。 “依赖属性信息”部分包含指向该依赖属性的 DependencyProperty 标识符字段的链接。 它还包含该属性的元数据选项列表、每个类的替代信息和其他详细信息。

依赖属性支持 CLR 属性

依赖属性和 WPF 属性系统通过提供一个支持属性的类型来扩展属性功能,这是使用专用字段支持属性的标准模式的替代方法。 此类型的名称为 DependencyProperty。 定义 WPF 属性系统的另一个重要类型是 DependencyObject,它定义了可以注册和拥有依赖属性的基类。

下面是一些常用的术语:

  • 依赖属性:由 DependencyProperty 提供支持的属性。

  • 依赖属性标识符:一个 DependencyProperty 实例,在注册依赖属性时以返回值的形式获取它,之后将其存储为类的静态成员。 许多与 WPF 属性系统交互的 API 使用依赖属性标识符作为参数。

  • CLR“包装器”:属性的 getset 实现。 这些实现通过在 GetValueSetValue 调用中使用依赖属性标识符来并入依赖属性标识符。 这样,WPF 属性系统就可以为属性提供支持。

以下示例定义 IsSpinning 依赖属性,以说明 DependencyProperty 标识符与它所支持的属性之间的关系。

public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
    "IsSpinning", typeof(bool),
    typeof(MainWindow)
    );

public bool IsSpinning
{
    get => (bool)GetValue(IsSpinningProperty);
    set => SetValue(IsSpinningProperty, value);
}
Public Shared ReadOnly IsSpinningProperty As DependencyProperty =
    DependencyProperty.Register("IsSpinning", GetType(Boolean), GetType(MainWindow))

Public Property IsSpinning As Boolean
    Get
        Return GetValue(IsSpinningProperty)
    End Get
    Set(value As Boolean)
        SetValue(IsSpinningProperty, value)
    End Set
End Property

属性及其支持性 DependencyProperty 字段的命名约定非常重要。 字段总是与属性同名,但其后面追加了 Property 后缀。 有关此约定及其原因的详细信息,请参阅自定义依赖属性

设置属性值

可以在代码或 XAML 中设置属性。

在 XAML 中设置属性值

以下 XAML 示例将按钮的背景色设置为红色。 XAML 属性的字符串值类型由 WPF XAML 分析程序转换为 WPF 类型。 在生成的代码中,WPF 类型为 Color(以 SolidColorBrush 的形式)。

<Button Content="I am red" Background="Red"/>

XAML 支持多种用于设置属性的语法形式。 要对特定的属性使用哪种语法取决于该属性所使用的值类型以及其他因素(例如,是否存在类型转换器)。 有关用于设置属性的 XAML 语法的详细信息,请参阅 WPF 中的 XAMLXAML 语法详述

以下 XAML 示例显示了另一个使用属性元素语法而不是特性语法的按钮背景。 XAML 不设置简单的纯色,而是将按钮 Background 属性设置为图像。 通过元素表示该图像,通过嵌套元素的属性指定图像来源。

<Button Content="I have an image background">
    <Button.Background>
        <ImageBrush ImageSource="stripes.jpg"/>
    </Button.Background>
</Button>

在代码中设置属性

在代码中设置依赖属性值通常只是调用由 CLR“包装器”公开的 set 实现:

Button myButton = new();
myButton.Width = 200.0;
Dim myButton As New Button With {
    .Width = 200.0
}

获取属性值实质上是在调用 get“包装器”实现:

double whatWidth = myButton.Width;
Dim whatWidth As Double = myButton.Width

还可以直接调用属性系统 API GetValueSetValue。 直接调用 API 适用于某些方案,但通常不适用于使用现有属性的情况。 通常,包装器更方便,并为开发人员工具提供更好的属性公开。

还可以在 XAML 中设置属性,然后通过代码隐藏在代码中访问这些属性。 有关详细信息,请参阅 WPF 中的代码隐藏和 XAML

由依赖属性提供的属性功能

与字段支持的属性不同,依赖属性扩展了属性的功能。 通常,添加的功能表示或支持以下功能之一:

资源

可以通过引用资源来设置依赖属性值。 资源通常指定为页面根元素或应用程序的 Resources 属性值,因为通过这些位置可以非常方便地访问资源。 在此示例中,我们定义一个 SolidColorBrush 资源:

<StackPanel.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</StackPanel.Resources>

现在资源已定义,我们可以引用资源来为 Background 属性提供值:

<Button Background="{DynamicResource MyBrush}" Content="I am gold" />

在 WPF XAML 中,可以使用静态或动态资源引用。 此特定资源作为 DynamicResource 引用。 动态资源引用只能用于设置依赖属性,因此它是由 WPF 属性系统明确启用的动态资源引用用法。 有关详细信息,请参阅 XAML 资源

注意

资源被视为本地值,这意味着,如果设置另一个本地值,该资源引用将被消除。 有关详细信息,请参阅依赖属性值优先级

数据绑定

依赖属性可以通过数据绑定来引用值。 数据绑定通过特定标记扩展语法(在 XAML 中)或 Binding 对象(在代码中)起作用。 使用数据绑定,最终属性值的确定将延迟到运行时,在运行时,将从数据源获取属性值。

以下示例使用在 XAML 中声明的绑定来设置 ButtonContent 属性。 该绑定使用继承的数据上下文和 XmlDataProvider 数据源(未显示)。 绑定本身通过 XPath 指定数据源中的源属性。

<Button Content="{Binding Source={StaticResource TestData}, XPath=test[1]/@text}"/>

注意

绑定被视为本地值,这意味着,如果设置另一个本地值,该绑定将被消除。 有关详细信息,请参阅依赖属性值优先级

依赖属性或 DependencyObject 类本身不支持通过 INotifyPropertyChanged 来通知数据绑定操作的 DependencyObject 源属性值的更改。 有关如何创建要用在数据绑定中并且可以向数据绑定目标报告更改的属性的详细信息,请参阅数据绑定概述

样式

之所以使用依赖属性,令人心动的原因在于样式和模板。 设置定义应用程序 UI 的属性时,样式尤其有用。 在 XAML 中,通常将样式定义为资源。 样式与属性系统交互,因为它们通常包含特定属性的“资源库”,以及基于另一个属性的运行时值更改属性值的“触发器”。

以下示例创建一个简单样式,该样式在 Resources 字典(未显示)内定义。 然后将该样式直接应用于 ButtonStyle 属性。 样式中的资源库将带样式 ButtonBackground 属性设置为绿色。

<Style x:Key="GreenButtonStyle">
    <Setter Property="Control.Background" Value="Green"/>
</Style>
<Button Style="{StaticResource GreenButtonStyle}" Content="I am green"/>

有关详细信息,请参阅样式设置和模板化

动画

可以对依赖属性进行动画处理。 当应用的动画运行时,动画值的优先级高于任何其他属性值,包括本地值。

以下示例对 ButtonBackground 属性进行动画处理。 从技术上说,属性元素语法将空白 SolidColorBrush 设置为 Background,并对 SolidColorBrushColor 属性进行动画处理。

<Button Content="I am animated">
    <Button.Background>
        <SolidColorBrush x:Name="AnimBrush"/>
    </Button.Background>
    <Button.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation
                        Storyboard.TargetName="AnimBrush" 
                        Storyboard.TargetProperty="(SolidColorBrush.Color)"
                        From="Blue" To="White" Duration="0:0:1" 
                        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 属性。 因此,子对象中的绑定无需指定源,它们可以使用父对象 StackPanelDataContext 的继承值。 或者,子对象可以直接在 Binding 中指定自己的 DataContextSource,而不使用继承值。

<StackPanel Canvas.Top="50" DataContext="{Binding Source={StaticResource TestData}}">
    <Button Content="{Binding XPath=test[2]/@text}"/>
</StackPanel>

有关详细信息,请参阅属性值继承

WPF 设计器集成

具有作为依赖属性实现的属性的自定义控件可以与适用于 Visual Studio 的 WPF 设计器很好地集成。 一个示例就是能够在“属性”窗口中编辑直接依赖属性和附加依赖属性。 有关详细信息,请参阅控件创作概述

依赖项属性值优先级

WPF 属性系统中任何基于属性的输入都可以设置依赖属性的值。 由于存在依赖属性值优先级,使得属性获取值的方式的各种方案得以按可预测的方式交互。

注意

SDK 文档在讨论依赖属性时有时会使用“本地值”或“本地设置的值”等术语。 本地设置的值是指在代码中直接为对象实例设置的属性值,或者在 XAML 中设置为元素特性的属性值。

下一个示例包含适用于任何按钮的 Background 属性的样式,但指定了一个具有本地设置的 Background 属性的按钮。 从技术上说,该按钮的 Background 属性设置了两次,但是仅应用一个值,即具有最高优先级的值。 本地设置的值具有最高优先级,对于正在运行的动画除外,但是在本示例中没有应用动画。 因此,第二个按钮使用 Background 属性的本地设置值,而不使用样式资源库值。 第一个按钮没有本地值或其他优先级高于样式资源库的值,因此使用 Background 属性的样式资源库值。

<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Orange"/>
        </Style>
    </StackPanel.Resources>
    <Button>I am styled orange</Button>
    <Button Background="Pink">I am locally set to pink (not styled orange)</Button>
</StackPanel>

为什么存在依赖属性优先级?

本地设置的值优先于样式资源库值,后者支持元素属性的本地控制。 有关详细信息,请参阅依赖属性值优先级

注意

为 WPF 元素定义的许多属性并不是依赖属性,因为依赖属性通常仅在需要 WPF 属性系统的某个功能时实现。 这些功能包括数据绑定、样式设置、动画、默认值支持、继承、附加属性和失效。

了解有关依赖属性的详细信息

  • 组件开发人员或应用程序开发人员可能希望创建自己的依赖属性来添加功能,例如数据绑定或样式支持,或失效和值强制转换支持。 有关详细信息,请参阅自定义依赖属性

  • 将依赖属性视为公共属性,可以由任何具有实例访问权限的调用方访问或发现。 有关详细信息,请参阅依赖属性安全性

  • 附加属性是一种支持 XAML 中的专用语法的属性。 附加属性通常与公共语言运行时属性没有 1:1 的对应关系,而且不一定是依赖属性。 附加属性的主要用途是允许子元素向其父元素报告属性值,即使父元素和子元素的类成员列表中没有该属性也是如此。 一个主要方案是使子元素能够告知父元素如何在 UI 中呈现它们。 有关示例,请参阅 DockLeft。 有关详细信息,请参阅附加属性概述

另请参阅