附加属性是一个 XAML 概念。 附加属性允许在对象上设置其他属性/值对,但属性不是原始对象定义的一部分。 附加属性通常定义为依赖属性的专用形式,该依赖属性在所有者类型的对象模型中没有传统的属性包装器。
先决条件
我们假设你理解依赖项属性的基本概念,并已阅读 依赖项属性概述。
XAML 中的附加属性
在 XAML 中,使用语法 AttachedPropertyProvider.PropertyName 设置附加属性。 下面是如何在 XAML 中设置 Canvas.Left 的示例。
<Canvas>
<Button Canvas.Left="50">Hello</Button>
</Canvas>
注释
我们只是使用 Canvas.Left 作为附加属性的示例,而不完全解释为何要使用它。 若要详细了解 Canvas.Left 的用途以及 Canvas 如何处理其布局子级,请参阅 Canvas 参考主题或 使用 XAML 定义布局。
为何使用附加属性?
附加属性是一种打破编码约定的方法,因为这些约定可能会阻碍关系中的不同对象在运行时相互传递信息。 当然,可以将属性放在通用基类上,以便每个对象只能获取和设置该属性。 最终,这些操作场景的庞大数量可能会使你的基类因可共享属性而膨胀。 它甚至可能会引入一种情况:在几百个后代中,只有两个试图使用某个属性。 这不是良好的类设计。 为了解决此问题,附加属性概念使对象能够为其自己的类结构未定义的属性赋值。 定义类可以在对象树中创建各种对象后,在运行时从子对象读取值。
例如,子元素可以使用附加属性来告知其父元素如何在 UI 中显示它们。 这是 Canvas.Left 附加属性的情况。 Canvas.Left 是附加属性,因为它设置在 Canvas 元素中包含的元素上,而不是设置在 Canvas 本身上。 然后,任何可能的子元素都使用 Canvas.Left 并 Canvas.Top 在 Canvas 布局容器父元素中指定其布局偏移量。 附加属性使得可以实现这种功能,而无需将基元素的对象模型填满大量仅适用于各种可能布局容器中的某一个的属性。 相反,许多布局容器实现其自己的附加属性集。
为了实现附加属性,Canvas 类定义名为 Canvas.LeftProperty 的静态 DependencyProperty 字段。 然后, Canvas 提供 SetLeft 和 GetLeft 方法作为附加属性的公共访问器,以启用 XAML 设置和运行时值访问。 对于 XAML 和依赖属性系统,这组 API 满足一种模式,该模式为附加属性启用特定的 XAML 语法,并将值存储在依赖属性存储中。
拥有者类型如何使用附加属性
尽管可以在任何 XAML 元素(或任何基础 DependencyObject)上设置附加属性,但这并不意味着设置该属性会产生一个有形的结果,或者访问该值。 定义附加属性的类型通常遵循以下方案之一:
- 定义附加属性的类型在对象关系中充当父对象。 子对象将设置附加属性的值。 附加属性所有者类型具有一些先天行为,这些行为遍历其子元素、获取值,并在对象生命周期的某个时刻对这些值执行操作(布局操作、SizeChanged 等)。
- 定义附加属性的类型用作各种可能的父元素和内容模型的子元素,但信息不一定是布局信息。
- 附加属性将信息报告给服务,而不是向另一个 UI 元素报告信息。
有关这些场景和拥有者类型的更多信息,请参阅“关于Canvas.Left的更多信息”部分的自定义附加属性。
代码中的附加属性
附加属性不像其他依赖属性那样,具备用于轻松获取和设置的典型属性包装器。 这是因为附加属性不一定是设置属性的实例的代码中心对象模型的一部分。 (虽然不常见,但允许定义一个属性,该属性既是附加属性,其他类型可以自行设置,而且在拥有类型上也有传统的属性用法。
可通过两种方法在代码中设置附加属性:使用属性系统 API 或使用 XAML 模式访问器。 这些技术在最终结果方面几乎等效,因此使用哪种技术主要是编码样式的问题。
使用属性系统
Windows 运行时的附加属性作为依赖属性实现,以便这些值可以存储在属性系统的共享依赖属性存储中。 因此,附加属性会在所属类中公开依赖属性的标识符。
若要在代码中设置附加属性,请调用 SetValue 方法,并传递 DependencyProperty 字段,该字段充当该附加属性的标识符。 (还要传递需要设置的值。)
若要获取代码中附加属性的值,请调用 GetValue 方法,再次传递用作标识符的 DependencyProperty 字段。
使用 XAML 访问器模式
XAML 处理器在将 XAML 解析为对象树时,必须能够设置附加属性值。 附加属性的所有者类型必须实现以 GetPropertyName 和 SetPropertyName 形式命名的专用访问器方法。 这些专用访问器方法也是在代码中获取或设置附加属性的一种方法。 从代码的角度来看,附加属性类似于具有方法访问器而不是属性访问器的后盾字段,并且支持字段可以存在于任何对象上,而无需专门定义。
下一个示例演示如何通过 XAML 访问器 API 在代码中设置附加属性。 在此示例中, myCheckBox 是 CheckBox 类的一个实例。 最后一行是实际设置值的代码;前面的行只是用来建立实例及其父子关系。 如果使用属性系统,则未注释的最后一行是语法。 如果使用 XAML 访问器模式,则注释的最后一行是语法。
Canvas myC = new Canvas();
CheckBox myCheckBox = new CheckBox();
myCheckBox.Content = "Hello";
myC.Children.Add(myCheckBox);
myCheckBox.SetValue(Canvas.TopProperty,75);
//Canvas.SetTop(myCheckBox, 75);
Dim myC As Canvas = New Canvas()
Dim myCheckBox As CheckBox= New CheckBox()
myCheckBox.Content = "Hello"
myC.Children.Add(myCheckBox)
myCheckBox.SetValue(Canvas.TopProperty,75)
' Canvas.SetTop(myCheckBox, 75)
Canvas myC;
CheckBox myCheckBox;
myCheckBox.Content(winrt::box_value(L"Hello"));
myC.Children().Append(myCheckBox);
myCheckBox.SetValue(Canvas::TopProperty(), winrt::box_value(75));
// Canvas::SetTop(myCheckBox, 75);
Canvas^ myC = ref new Canvas();
CheckBox^ myCheckBox = ref new CheckBox();
myCheckBox->Content="Hello";
myC->Children->Append(myCheckBox);
myCheckBox->SetValue(Canvas::TopProperty,75);
// Canvas::SetTop(myCheckBox, 75);
自定义附加属性
有关如何定义自定义附加属性的代码示例,以及有关使用附加属性的方案的详细信息,请参阅 自定义附加属性。
附加属性引用的特殊语法
附加属性名称中的点是标识模式的关键部分。 当语法或情况将点视为具有其他含义时,有时存在歧义。 例如,将点视为绑定路径的对象模型遍历。 在大多数涉及此类歧义的情况下,附加属性有一种特殊的语法,使内部点仍然解析为所有者。属性的附加属性分隔符。
- 若要将附加属性指定为动画的目标路径的一部分,请将附加的属性名称括在括号
()中,例如(Canvas.Left)。 有关详细信息,请参阅 属性路径语法。
警告
Windows 运行时 XAML 实现的现有限制是无法对自定义附加属性进行动画处理。
- 若要将附加属性指定为资源文件中对 x:Uid 的资源引用的目标属性,请使用一种特殊语法,在方括号“[]”内注入代码样式的完全限定 using: 声明,以故意创建作用域中断。 例如,假设存在一个元素
<TextBlock x:Uid="Title" />,则针对该实例的 Canvas.Top 值的资源文件中的资源键为Title.\[using:Microsoft.UI.Xaml.Controls\]Canvas.Top。 有关资源文件和 XAML 的详细信息,请参阅 UI 中的本地化字符串。