依赖项属性概述 (WPF .NET)

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

先决条件

本文假定对 .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 中的 XAML 语法XAML 语法的详细信息

下面的 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 中的资源。 样式与属性系统交互,因为它们通常包含特定属性的“setters”,以及基于其他属性的运行时值来更改属性值的“triggers”。

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

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

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

动画

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

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

<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 引用中该依赖属性的 Dependency Property Information 节来检查依赖属性是否继承。

以下示例显示了一个绑定,其中包含用于指定绑定源的 DataContext 属性。 因此,子对象中的绑定不需要指定源,并且可以使用父 StackPanel 对象中 DataContext 继承的值。 或者,子对象可以直接在 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。 有关详细信息,请参阅 附加属性概述

另请参阅