附加属性概述

更新:2007 年 11 月

附加属性是可扩展应用程序标记语言 (XAML) 定义的一个概念。附加属性旨在用作可在任何对象上设置的一类全局属性。在 Windows Presentation Foundation (WPF) 中,附加属性通常定义为没有常规属性“包装”的一种特殊形式的依赖项属性。

本主题包括下列各节。

  • 先决条件
  • 为何使用附加属性
  • XAML 中的附加属性
  • 所属类型如何使用附加属性
  • 代码中的附加属性
  • 附加属性元数据
  • 自定义的附加属性
  • 了解有关附加属性的更多信息
  • 相关主题

先决条件

本主题假定您从 Windows Presentation Foundation (WPF) 类的现有依赖项属性的使用者角度了解依赖项属性,并且已阅读依赖项属性概述。若要采用本主题中的示例,还应当了解可扩展应用程序标记语言 (XAML) 并知道如何编写 WPF 应用程序。

为何使用附加属性

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

XAML 中的附加属性

在 XAML 中,通过使用语法 AttachedPropertyProvider.属性名 来设置附加属性。

下面是在 XAML 中设置 DockPanel.Dock 的方法的一个示例:

<DockPanel>
  <CheckBox DockPanel.Dock="Top">Hello</CheckBox>
</DockPanel>

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

此外,因为 XAML 中的附加属性 (Property) 是您在标记中设置的属性 (Attribute),所以只有集运算具有相关性。尽管有一些可用于对值进行比较的间接机制,例如样式中的触发器(有关详细信息,请参见样式设置和模板化),但您不能直接在 XAML 中获取属性。

WPF 中的附加属性实现

在 Windows Presentation Foundation (WPF) 中,WPF 类型上存在的大多数附加属性都实现为依赖项属性。附加属性是一个 XAML 概念,而依赖项属性则是一个 WPF 概念。因为 WPF 附加属性是依赖项属性,所以它们支持依赖项属性概念,例如,属性元数据以及这些属性元数据中的默认值。

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

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

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

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

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

父级定义的附加属性示例

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

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

代码中的附加属性

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

下面的示例演示如何在代码中设置附加属性。在本示例中,myCheckBox 是 CheckBox 类的一个实例。

DockPanel myDockPanel = new DockPanel();
CheckBox myCheckBox = 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 2008 WPF 设计器支持,如“属性”窗口编辑。有关更多信息,请参见控件创作概述

前面已提到,如果您希望使用属性值继承,应注册为一个附加属性。

如何创建附加属性

如果您的类将附加属性严格定义为用于其他类型,那么该类不必从 DependencyObject 派生。但是,如果您遵循使附加属性同时也是一个依赖项属性的整体 WPF 模型,则需要从 DependencyObject 派生。

通过声明一个 DependencyProperty 类型的 public static readonly 字段将附加属性定义为一个依赖项属性。通过使用 RegisterAttached 方法的返回值来定义此字段。为了遵循命名标识字段及其所表示的属性的已建立 WPF 模式,字段名必须与附加属性名一致,并附加字符串 Property。附加属性提供程序还必须提供静态的 Get属性名 和 Set属性名 方法作为附加属性访问器,否则会导致属性系统无法使用您的附加属性。

Get 访问器

Get属性名 访问器的签名必须是:

public static object Get属性名(object target)

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

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

Set 访问器

Set属性名 访问器的签名必须是:

public static void Set属性名(object target, object value)

  • 在实现中可以将 target 对象指定为更具体的类型。例如,SetDock 方法将它的类型定义为 UIElement,因为附加属性只能在 UIElement 实例上设置。

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

下面的示例演示了依赖项属性注册(使用 RegisterAttached 方法)以及 Get属性名 和 Set属性名 访问器。在本示例中,附加属性名是 IsBubbleSource。因此,访问器的名称必须为 GetIsBubbleSource 和 SetIsBubbleSource。

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);
}

附加属性特性

WPF 定义了几个 .NET Framework 属性,这些属性旨在向反射进程以及反射的典型用户和诸如设计器这样的属性信息提供有关附加属性的信息。因为附加属性具有一种类型的不受限范围,所以设计器需要一种方式来避免为用户提供在使用 XAML 的特定技术实现中定义的所有附加属性的全局列表。WPF 为附加属性定义的 .NET Framework 属性 可以用于规定应当在属性窗口中显示给定的附加属性的情形。您可能会考虑也对自己的自定义附加属性 (Property) 应用这些属性 (Attribute). 相应的参考页中介绍了 .NET Framework 属性的用途和语法:

了解有关附加属性的更多信息

  • 有关创建附加属性的更多信息,请参见如何:注册附加属性

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

  • 您还可以将属性注册为附加属性和依赖项属性,但之后仍然需要公开“包装”实现。在这种情况下,既可以在该元素上设置属性,也可以通过 XAML 附加属性语法在任何元素上设置属性。具有同时适用于标准用法和附加用法的方案的属性示例是 FrameworkElement.FlowDirection

请参见

任务

如何:注册附加属性

概念

依赖项属性概述

自定义的依赖项属性

XAML 概述

参考

DependencyProperty