附加属性概述

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

先决条件

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

为何使用附加属性

附加属性允许子元素为父元素中定义的属性指定唯一值。 常见方案是一个子元素,用于指定其父元素应如何在 UI 中呈现该元素。 例如, DockPanel.Dock 附加属性是因为它在子 DockPanel元素上设置,而不是 DockPanel 本身。 该 DockPanel 类定义一个静态 DependencyProperty 字段,命名 DockProperty,然后提供 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>

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

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.Dock的子元素设置的值会影响呈现,并且无论在将子元素添加到DockPanel之前或之后设置该值,呈现效果都将保持相同。

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

附加属性元数据

附加属性的元数据通常与依赖属性没有什么不同。 注册附加属性时,用于 FrameworkPropertyMetadata 指定属性的特征,例如该属性是否影响呈现或度量。 当你通过重写附加属性的元数据来指定默认值时,该值将成为重写类实例中隐式附加属性的默认值。 如果没有通过其他方式设置附加属性值,那么当某个类的实例使用Get<property name>访问器来查询属性时,将报告该属性的默认值,该类是您指定了元数据的类。

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

自定义附加属性

何时创建附加属性

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

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

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

  • 你希望 Visual Studio WPF 设计器支持,例如能够通过 “属性” 窗口编辑属性。 有关详细信息,请参阅 控件创作概述

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

如何创建附加属性

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

通过声明 public static readonly 类型的 DependencyProperty字段,将附加属性定义为定义类中的依赖项。 然后,将方法的 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是读取附加属性的DependencyObject来源。 类型 target 可以比 DependencyObject 更具体。 例如,DockPanel.GetDock 访问器方法将 target 类型化为 UIElement,因为附加属性旨在在UIElement 实例上进行设置。 UiElement 间接派生自 DependencyObject.
  • 返回类型可以比 object 更具体。 例如,方法GetDock将返回值的类型指定为Dock,因为返回值应为Dock枚举类型。

注释

get的附加属性访问器对于设计工具中的数据绑定支持是必需的,例如 Visual Studio 或 Blend for Visual Studio。

Set 访问器

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

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

附加属性

WPF 定义了多个 .NET 属性,这些属性向反射进程以及反射和属性信息的使用者(如设计器)提供有关附加属性的信息。 设计师使用 WPF 定义的 .NET 属性,以限制在属性窗口中显示的属性,为了避免用全局列表显示所有附加属性时可能对用户造成的压倒性负担。 可以考虑将这些属性应用于自己的自定义附加属性。 这些参考页描述了 .NET 属性的用途和语法:

了解详细信息

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

另请参阅