附加属性概述 (WPF .NET)

附加属性是可扩展应用程序标记语言(XAML)概念。 附加属性允许在派生自 DependencyObject的任何 XAML 元素上设置额外的属性/值对,即使该元素在其对象模型中未定义这些额外属性。 额外的属性可以全局访问。 附加属性通常被定义为依赖属性的一种特殊形式,它没有传统的属性包装器。

先决条件

本文假设对依赖属性有一个基本的了解,并且你已阅读 依赖项属性概述。 若要遵循本文中的示例,如果你熟悉 XAML 并知道如何编写 Windows Presentation Foundation (WPF) 应用程序,它很有帮助。

为何使用附加属性

附加属性允许子元素为父元素中定义的属性指定唯一值。 常见方案是一个子元素,用于指定其父元素应如何在 UI 中呈现该元素。 例如,DockPanel.Dock 是附加属性,因为它在 DockPanel的子元素上设置,而不是 DockPanel 本身。 DockPanel 类定义一个名为 DockProperty的静态 DependencyProperty 字段,然后将 GetDockSetDock 方法作为附加属性的公共访问器提供。

XAML 中的附加属性

在 XAML 中,使用语法 <attached property provider type>.<property name>设置附加属性,其中附加属性提供程序是定义附加属性的类。 以下示例演示 DockPanel 的子元素如何设置 DockPanel.Dock 属性值。

<DockPanel>
    <TextBox DockPanel.Dock="Top">Enter text</TextBox>
</DockPanel>

这种用法类似于静态属性,因为它引用的是拥有并注册附加属性的类型(例如,DockPanel),而不是实例名称。

使用 XAML 属性指定附加属性时,仅设置操作适用。 不能通过 XAML 直接获取属性值,尽管有一些间接机制用于比较值,例如样式中的 触发器。

WPF 中的附加属性

附加属性是 XAML 概念,依赖属性是 WPF 概念。 在 WPF 中,WPF 类型上大多数与 UI 相关的附加属性都作为依赖属性实现。 作为依赖属性实现的 WPF 附加属性支持依赖属性概念,例如属性元数据,包括元数据中的默认值。

附加的属性使用模型

尽管任何对象都可以设置附加属性值,但这并不意味着设置值将产生一个有形的结果,或者该值将由另一个对象使用。 附加属性的主要用途是为各种类层次结构和逻辑关系中的对象提供一种方法,以便将公共信息报告给定义附加属性的类型。 附加属性用法通常遵循以下模型之一:

  • 定义附加属性的类型是设置附加属性值的元素的父级。 父类型通过在对象树结构上作用的内部逻辑遍历其子对象,获取值,并以某种方式对这些值进行操作。
  • 定义附加属性的类型用作各种可能的父元素和内容模型的子元素。
  • 定义附加属性的类型表示服务。 其他类型设定附加属性的值。 然后,在服务上下文中计算设置属性的元素时,附加的属性值是通过服务类的内部逻辑获取的。

由父元素定义的附加属性示例

WPF 定义附加属性的典型方案是在父元素支持子元素集合时,父元素基于其每个子元素报告的数据实现行为。

DockPanel 定义 DockPanel.Dock 附加属性。 DockPanel 具有类级代码,特别是 MeasureOverrideArrangeOverride,这是其呈现逻辑的一部分。 DockPanel 实例检查是否有其任何直接子元素为 DockPanel.Dock设置了一个值。 如果是这样,这些值将成为应用于每个子元素的呈现逻辑的输入。 尽管从理论上讲,附加属性可以影响除直接父级之外的元素,但嵌套 DockPanel 实例的定义行为只是与其直接子元素集合进行交互。 因此,如果在没有 DockPanel 父级的元素上设置 DockPanel.Dock,则不会引发错误或异常,这样您就会创建一个不会被任何 DockPanel使用的全局属性值。

代码中的附加属性

WPF 中的附加属性没有典型的 CLR getset 包装器方法,因为属性可能从 CLR 命名空间外部设置。 若要允许 XAML 处理器在分析 XAML 时设置这些值,定义附加属性的类必须以 Get<property name>Set<property name>的形式实现专用访问器方法。

还可以使用专用访问器方法在代码中获取和设置附加属性,如以下示例所示。 在此示例中,myTextBoxTextBox 类的实例。

DockPanel myDockPanel = new();
TextBox myTextBox = new();
myTextBox.Text = "Enter text";

// Add child element to the DockPanel.
myDockPanel.Children.Add(myTextBox);

// Set the attached property value.
DockPanel.SetDock(myTextBox, Dock.Top);
Dim myDockPanel As DockPanel = New DockPanel()
Dim myTextBox As TextBox = New TextBox()
myTextBox.Text = "Enter text"

' Add child element to the DockPanel.
myDockPanel.Children.Add(myTextBox)

' Set the attached property value.
DockPanel.SetDock(myTextBox, Dock.Top)

如果不将 myTextBox 添加为 myDockPanel的子元素,则调用 SetDock 不会引发异常或产生任何影响。 只有对 DockPanel 的子元素设置的 DockPanel.Dock 值会影响呈现,无论在将子元素添加到 DockPanel之前还是之后设置值,呈现都将相同。

从代码的角度来看,附加属性类似于具有方法访问器而不是属性访问器的后备字段,并且可以在任何对象上设置,而无需首先在这些对象上定义。

附加的属性元数据

附加属性的元数据通常与依赖属性没有区别。 注册附加属性时,请使用 FrameworkPropertyMetadata 指定属性的特征,例如该属性是否影响呈现或度量。 当您通过重写附加属性的元数据来指定默认值时,该值将成为重写类中隐式附加属性的默认值。 如果未设置附加属性值,则当使用 Get<property name> 访问器与指定元数据的类的实例查询属性时,将报告默认值。

若要对属性启用属性值继承,请使用附加属性,而不是非附加依赖属性。 有关详细信息,请参阅 属性值继承

自定义附加属性

何时创建附加属性

创建附加属性在以下情况下非常有用:

  • 你需要一个属性设置机制,可用于定义类以外的类。 常见方案适用于 UI 布局,例如 DockPanel.DockPanel.ZIndexCanvas.Top 都是现有布局属性的示例。 在布局方案中,布局控制元素的子元素能够向布局父元素表达布局要求,并为父元素定义的附加属性设置值。

  • 其中一个类表示服务,并且希望其他类更透明地集成服务。

  • 你希望 Visual Studio WPF 设计器提供支持,例如在 属性 窗口中编辑特定属性的功能。 有关详细信息,请参阅 控件创作概述

  • 你想要使用属性值继承。

如何创建附加属性

如果类只定义附加属性供其他类型的使用,则类不需要从 DependencyObject派生。 否则,请遵循 WPF 模型,使附加属性也是一个依赖属性,并从 DependencyObject派生您的类。

通过声明 DependencyProperty类型的 public static readonly 字段,将附加属性定义为定义类中的依赖项。 然后,将 RegisterAttached 方法的返回值分配给字段,该字段也称为 依赖属性标识符。 按照 WPF 属性命名约定,通过命名标识符字段 <property name>Property来区分字段与其表示的属性。 此外,提供静态 Get<property name>Set<property name> 访问方法,使属性系统能够访问附加属性。

以下示例演示如何使用 RegisterAttached 方法注册依赖属性,以及如何定义访问器方法。 在此示例中,附加属性的名称 HasFish,因此标识符字段命名为 HasFishProperty,访问器方法命名为 GetHasFishSetHasFish

public class Aquarium : UIElement
{
    // Register an attached dependency property with the specified
    // property name, property type, owner type, and property metadata.
    public static readonly DependencyProperty HasFishProperty = 
        DependencyProperty.RegisterAttached(
      "HasFish",
      typeof(bool),
      typeof(Aquarium),
      new FrameworkPropertyMetadata(defaultValue: false,
          flags: FrameworkPropertyMetadataOptions.AffectsRender)
    );

    // Declare a get accessor method.
    public static bool GetHasFish(UIElement target) =>
        (bool)target.GetValue(HasFishProperty);

    // Declare a set accessor method.
    public static void SetHasFish(UIElement target, bool value) =>
        target.SetValue(HasFishProperty, value);
}
Public Class Aquarium
    Inherits UIElement

    ' Register an attached dependency property with the specified
    ' property name, property type, owner type, and property metadata.
    Public Shared ReadOnly HasFishProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("HasFish", GetType(Boolean), GetType(Aquarium),
            New FrameworkPropertyMetadata(defaultValue:=False,
                flags:=FrameworkPropertyMetadataOptions.AffectsRender))

    ' Declare a get accessor method.
    Public Shared Function GetHasFish(target As UIElement) As Boolean
        Return target.GetValue(HasFishProperty)
    End Function

    ' Declare a set accessor method.
    Public Shared Sub SetHasFish(target As UIElement, value As Boolean)
        target.SetValue(HasFishProperty, value)
    End Sub

End Class

Get 访问器

get 访问器方法的签名是 public static object Get<property name>(DependencyObject target),其中:

  • target 中读取附加属性的元素是 DependencyObjecttarget 类型比 DependencyObject更具体。 例如,DockPanel.GetDock 访问器方法将 target 类型为 UIElement,因为附加属性旨在设置在 UIElement 实例上。 UiElement 间接派生自 DependencyObject
  • 返回类型可以比 object更具体。 例如,GetDock 方法将返回的值键入为 Dock,因为返回值应该是 Dock 枚举。

注意

在设计工具(如 Visual Studio 或 Blend for Visual Studio)中,为了支持数据绑定,附加属性需要 get 访问器。

Set 访问器

set 访问器方法签名 public static void Set<property name>(DependencyObject target, object value),其中:

  • target 是写入附加属性的 DependencyObjecttarget 类型比 DependencyObject更具体。 例如,SetDock 方法将 target 类型为 UIElement,因为附加属性旨在设置在 UIElement 实例上。 UiElement 间接派生自 DependencyObject
  • value 类型比 object更具体。 例如,SetDock 方法需要 Dock 值。 XAML 加载程序需要能够从表示附加属性值的标记字符串生成 value 类型。 因此,您使用的类型必须支持类型转换、值序列化或标记扩展。

附加属性

WPF 定义了多个 .NET 属性,这些属性向反射进程以及反射和属性信息的使用者(如设计器)提供有关附加属性的信息。 设计人员使用 WPF 定义的 .NET 属性来限制在属性窗口中显示的属性,以避免用户被所有附加属性的全球列表所淹没。 可以考虑将这些属性应用于自己的自定义附加属性。 这些参考页描述了 .NET 属性的用途和语法:

了解更多信息

  • 有关创建附加属性的详细信息,请参阅 注册附加属性
  • 有关依赖项属性和附加属性的更高级使用方案,请参阅 自定义依赖项属性
  • 可以将一个属性同时注册为附加属性和依赖属性,并包含常规的属性包装器。 这样,可以使用属性包装器在元素上设置属性,也可以使用 XAML 附加属性语法在任何其他元素上设置属性。 有关示例,请参阅 FrameworkElement.FlowDirection

另请参阅