附加属性概述

附加属性是由 XAML 定义的概念。 附加属性旨在用作可在任何依赖项对象上设置的一类全局属性。 在 Windows Presentation Foundation (WPF) 中,附加属性通常定义为没有常规属性“包装器”的依赖属性的专用形式。

先决条件

本文假设你从 Windows Presentation Foundation (WPF) 类上现有依赖属性的使用者角度已对依赖属性有所了解,并已阅读依赖属性概述。 为理解本文中的示例,还应了解 XAML 并知道如何编写 WPF 应用程序。

为何使用附加属性

附加属性的一个用途是允许不同的子元素为在父元素中定义的属性指定唯一值。 此方案的一个具体应用是,让子元素通知父元素它们在用户界面 (UI) 中的呈现方式。 一个示例是 DockPanel.Dock 属性。 将 DockPanel.Dock 属性创建为附加属性,因为它旨在对包含在 DockPanel 中的元素进行设置,而不是对 DockPanel 本身进行设置。 DockPanel 类定义了名为 DockProperty 的静态 DependencyProperty 字段,然后提供了 GetDockSetDock 方法作为附加属性的公共访问器。

XAML 中的附加属性

在 XAML 中,可以使用语法 AttachedPropertyProvider.PropertyName 来设置附加属性

下面的示例说明如何在 XAML 中设置 DockPanel.Dock

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

此用法与静态属性有些类似,即总是引用拥有并注册附加属性的类型 DockPanel,而不是引用通过名称指定的任何实例。

此外,由于 XAML 中的附加属性是在标记中设置的属性,因此,只有设置操作具有相关性。 尽管存在一些用于比较值的间接机制(如在样式中触发),但无法直接在 XAML 中直接获取属性(有关详细信息,请参阅样式设置和模板化)。

WPF 中的附加属性实现

在 Windows Presentation Foundation (WPF) 中,WPF 类型上的大多数与 UI 相关的附加属性都作为依赖属性实现。 附加属性是个 XAML 概念,而依赖属性则是个 WPF 概念。 因为 WPF 附加属性是依赖属性,所以它们支持依赖属性概念,如属性元数据和属性元数据默认值。

所属类型 如何使用附加属性

尽管可以在任何对象上设置附加属性,但这并不自动意味着设置该属性会产生实际的结果,或者该值会被其他对象使用。 通常,附加属性是为了使来自各种可能的类层次结构或逻辑关系的对象都可以向用于定义附加属性的类型报告公用信息。 定义附加属性的类型通常采用以下模型之一:

  • 设计定义附加属性的类型,以便它可以是将为附加属性设置值的元素的父元素。 随后,该类型将在内部逻辑中对照某些对象树结构循环访问其子对象,获取值,并以某种方式作用于这些值。

  • 定义附加属性的类型将用作各种可能的父元素和内容模型的子元素。

  • 定义附加属性的类型表示一项服务。 其他类型为该附加属性设置值。 然后,当在服务的上下文中计算设置该属性的元素时,将通过服务类的内部逻辑获取附加属性的值。

父级定义的附加属性示例

WPF 定义附加属性的最典型方案是:父元素支持子元素集合,并实现分别为每个子元素报告行为细节的行为。

DockPanel 定义 DockPanel.Dock 附加属性,且 DockPanel 具有类级代码作为其呈现逻辑的一部分(具体是 MeasureOverrideArrangeOverride)。 DockPanel 实例将始终检查它,以查看其任何直接子元素是否为 DockPanel.Dock 设置了值。 如果已设置,这些值将变为应用于该特定子元素的呈现逻辑的输入。 嵌套的 DockPanel 实例处理它们各自的直接子元素集合,但是这种行为是特定于如何DockPanel处理DockPanel.Dock值来实现的。 理论上,可以有影响直接父级之外的元素的附加属性。 如果对某个元素设置了 DockPanel.Dock 附加属性,而该元素没有 DockPanel 父元素对其进行操作,则不会引发错误或异常。 这仅仅意味着设置了全局属性值,但它没有可以使用这一信息的当前 DockPanel 父级。

代码 中的附加属性

WPF 中的附加属性没有用于简化 get/set 访问的典型 CLR“包装”方法。 这是因为附加属性不是必须属于设置它的实例的 CLR 命名空间的一部分。 但是,分析 XAML 时,XAML 处理器必须能够设置这些值。 若要支持有效的附加属性用法,附加属性的所有者类型必须在“获取 PropertyName”和“设置 PropertyName”窗体中实现专用访问器方法。 这些专用访问器方法对在代码中设置附加属性也很有帮助。 从代码的角度来看,附加属性类似于具有方法访问器而不是属性访问器的支持字段,且支持字段可在任何对象上存在,无需专门定义。

下面的示例演示如何在代码中设置附加属性。 在此示例中,myCheckBoxCheckBox 类的实例。

DockPanel myDockPanel = new DockPanel();
CheckBox myCheckBox = new CheckBox();
myCheckBox.Content = "Hello";
myDockPanel.Children.Add(myCheckBox);
DockPanel.SetDock(myCheckBox, Dock.Top);
Dim myDockPanel As New DockPanel()
Dim myCheckBox As New CheckBox()
myCheckBox.Content = "Hello"
myDockPanel.Children.Add(myCheckBox)
DockPanel.SetDock(myCheckBox, Dock.Top)

与 XAML 用例类似,如果 myCheckBox 尚未由第四行代码将其添加为 myDockPanel 的子元素,则第五行代码不会引发异常,但该属性值不会与 DockPanel 父级交互,因此也就不会执行任何操作。 只有当对子元素设置了 DockPanel.Dock 值且存在 DockPanel 父级元素时,才会在呈现的应用程序中产生有效行为。 (在这种情况下,可以设置附加属性,然后附加到树。或者可以附加到树,然后设置附加属性。任一操作顺序均得出相同结果。)

附加属性元数据

注册该属性时,设置 FrameworkPropertyMetadata 以指定属性的特征,如属性是否会影响呈现、度量等。 附加属性的元数据通常与依赖属性上的元数据基本上都相同。 如果在附加属性元数据替代中指定默认值,该值将成为替代类实例上显式附加属性的默认值。 具体而言,当某些进程通过该属性的 Get 方法访问器请求附加属性值,并指定在其中指定元数据的类的示例时,将报告默认值,而不会设置该附加属性的值。

如果希望对属性启用属性值继承,应使用附加属性,而不是非附加的依赖属性。 有关详细信息,请参阅属性值继承

自定义附加属性

何时创建附加属性

当需要有一个可用于定义类之外的其他类的属性设置机制时,建议创建附加属性。 对于这一情况,最常见的方案是布局。 现有布局属性的示例包括 DockPanel.DockPanel.ZIndexCanvas.Top。 这里启用的方案是,作为布局控制元素的子元素存在的元素能够分别向其布局父级元素表达布局要求,其中每个元素都设置一个被父级定义为附加属性的属性值。

使用附加属性的另一种情况是,你的类表示一种服务,且你希望类能够以更透明的方式继承该服务。

但还有一种情况是接收 Visual Studio WPF 设计器支持,例如“属性”窗口编辑。 有关详细信息,请参阅控件创作概述

如前文所述,如果想要使用属性值继承,你应该注册为附加属性。

如何创建附加属性

如果你的类将附加属性严格定义为用于其他类型,则该类不必从 DependencyObject 派生。 但如果遵循使附加属性也作为依赖属性的总体 WPF 模型,则确实需要从 DependencyObject 派生。

通过声明 DependencyProperty 类型的 public static readonly 字段将附加属性定义为依赖属性。 可以使用 RegisterAttached 方法的返回值来定义此字段。 字段名称必须匹配附加属性名称,且追加字符串 Property,以遵循命名标识字段及其所表示的属性的 WPF 模式。 附加属性提供程序还必须提供静态的“获取 PropertyName”和“设置 PropertyName”方法,作为附加属性访问器,否则将导致属性系统无法使用附加属性

注意

如果省略附加属性的 get 访问器,该属性的数据绑定在设计工具(如 Visual Studio 和 Blend for Visual Studio)中将无法工作。

Get 访问器

“获取 PropertyName”访问器的签名必须如下所示

public static object GetPropertyName(object target)

  • target 对象在实现中可以指定为更具体的类型。 例如,DockPanel.GetDock 方法将该参数的类型设置为 UIElement,因为附加属性仅用于设置 UIElement 实例。

  • 返回值在实现中可以指定为更具体的类型。 例如,GetDock 方法将其类型设置为 Dock,因为此值只能设置为该枚举。

Set 访问器

“设置 PropertyName”访问器的签名必须如下所示

public static void SetPropertyName(object target, object value)

  • target 对象在实现中可以指定为更具体的类型。 例如,SetDock 方法将其类型设置为 UIElement,因为附加属性仅用于设置 UIElement 实例。

  • value 对象在实现中可以指定为更具体的类型。 例如,SetDock 方法将其类型设置为 Dock,因为此值只能设置为该枚举。 请记住,此方法的值是 XAML 加载器在标记中的附加属性用法中遇到附加属性时的输入。 该输入是在标记中指定为 XAML 属性值的值。 因此必须存在可用于你所使用的类型的类型转换、值序列化程序或标记扩展支持,以便可以从属性值(最终仅仅是一个字符串)创建相应的类型。

下面的示例介绍了依赖属性注册(使用 RegisterAttached 方法),以及“获取 PropertyName”和“设置 PropertyName”访问器。 在此示例中,附加属性名称为 IsBubbleSource。 因此,访问器必须名为 GetIsBubbleSourceSetIsBubbleSource

public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(
  "IsBubbleSource",
  typeof(Boolean),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetIsBubbleSource(UIElement element, Boolean value)
{
  element.SetValue(IsBubbleSourceProperty, value);
}
public static Boolean GetIsBubbleSource(UIElement element)
{
  return (Boolean)element.GetValue(IsBubbleSourceProperty);
}
Public Shared ReadOnly IsBubbleSourceProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsBubbleSource", GetType(Boolean), GetType(AquariumObject), New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.AffectsRender))
Public Shared Sub SetIsBubbleSource(ByVal element As UIElement, ByVal value As Boolean)
    element.SetValue(IsBubbleSourceProperty, value)
End Sub
Public Shared Function GetIsBubbleSource(ByVal element As UIElement) As Boolean
    Return CType(element.GetValue(IsBubbleSourceProperty), Boolean)
End Function

附加属性特性

WPF 定义了多个 .NET 属性,后者用于向反射进程和反射的典型用户提供关于附加属性的信息以及如设计器等属性信息。 由于附加属性的类型没有范围限制,因此设计者需要一种方法来避免用户查看全局列表时,看到使用 XAML 的特定技术实现中定义的所有附加属性。 WPF 为附加属性定义的 .NET 属性可用于限定应在属性窗口中显示给定附加属性的情况的范围。 你还可考虑对自己的自定义附加属性应用这些特性。 有关 .NET 属性的用途和语法说明,请参阅相应参考页面:

深入了解附加属性

  • 有关如何创建附加属性的详细信息,请参阅注册附加属性

  • 有关依赖属性和附加属性的更多高级使用方案,请参阅自定义依赖属性

  • 还可将属性注册为附加属性和依赖属性,但仍需公开“包装器”实现。 在这种情况下,属性可在该元素上设置,也可通过 XAML 附加属性语法在任何元素上设置。 FrameworkElement.FlowDirection 是一个为标准用法和附加用法提供适当场景的属性示例。

另请参阅