依赖属性元数据 (WPF .NET)

Windows Presentation Foundation (WPF) 属性系统包括一个依赖属性元数据报告系统。 通过元数据报告系统提供的信息超过了通过反射或通用公共语言运行时 (CLR) 特征提供的信息。 注册依赖属性时,可以选择创建元数据并将元数据分配给它。 如果从定义依赖属性的类派生,则可以替代继承依赖属性的元数据。 如果将类添加为依赖属性的所有者,也可以替代继承依赖属性的元数据。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

先决条件

本文假定你对依赖属性有基本的了解,并且已阅读依赖属性概述。 若要理解本文中的示例,还应当熟悉 Extensible Application Markup Language (XAML) 并知道如何编写 WPF 应用程序。

如何使用元数据

可以通过查询依赖属性元数据来检查依赖属性的特征。 当属性系统处理依赖属性时,它会访问该属性的元数据。 依赖属性的元数据对象包含以下类型的信息:

  • 依赖属性的默认值,当没有其他值(例如本地值、样式值或继承值)适用时由属性系统设置。 有关依赖属性值的运行时分配期间值优先级的详细信息,请参阅依赖属性值优先级

  • 对所有者类型上的强制转换值回调和属性更改回调的引用。 只能获取对具有 public 访问修饰符或在允许访问范围内的回调的引用。 有关依赖属性回调的详细信息,请参阅依赖属性回调和验证

  • WPF 框架级依赖属性特征(如果依赖属性是 WPF 框架属性)。 WPF 进程(如框架布局引擎和属性继承逻辑)将查询 WPF 框架级元数据。 有关详细信息,请参阅框架属性元数据

元数据 API

PropertyMetadata 类存储属性系统使用的大部分元数据。 可以通过以下类型创建和分配元数据实例:

  • 向属性系统注册依赖属性的类型。

  • 从定义依赖属性的类继承的类型。

  • 将自身添加为依赖属性所有者的类型。

如果某个类型在没有指定元数据的情况下注册了依赖属性,属性系统会将具有该类型的默认值的 PropertyMetadata 对象分配给依赖属性。

若要检索依赖属性的元数据,请对 DependencyProperty 标识符调用 GetMetadata 重载之一。 元数据作为 PropertyMetadata 对象返回。

不同的体系结构领域存在派生自 PropertyMetadata 的更具体的元数据类。 例如,UIPropertyMetadata 支持动画报告,FrameworkPropertyMetadata 支持 WPF 框架属性。 依赖属性也可以注册到 PropertyMetadata 派生类中。 尽管 GetMetadata 返回 PropertyMetadata 对象,但如果适用,你可以强制转换为派生类型,以检查特定于类型的属性。

FrameworkPropertyMetadata 公开的属性特征有时称为标志。 创建 FrameworkPropertyMetadata 实例时,可以选择将枚举类型 FrameworkPropertyMetadataOptions 的实例传递给 FrameworkPropertyMetadata 构造函数。 FrameworkPropertyMetadataOptions 允许按位组合指定元数据标记。 FrameworkPropertyMetadata 通过 FrameworkPropertyMetadataOptions 使构造函数签名保持合理的长度。 注册依赖属性时,在 FrameworkPropertyMetadataOptions 上设置的元数据标记在 FrameworkPropertyMetadata 中作为 Boolean 属性公开,而不是作为标记的位组合公开,从而使查询元数据特征更加直观。

替代或创建新的元数据?

继承依赖属性时,可以选择通过替代元数据来更改依赖属性的特征。 但是,可能无法始终通过替代元数据来完成依赖属性方案,有时需要在类中使用新的元数据定义自定义依赖属性。 自定义依赖属性与 WPF 类型定义的依赖属性具有相同的功能。 有关详细信息,请参阅自定义依赖属性

不能替代的依赖属性的一个特征就是它的值类型。 如果继承依赖属性具有所需的近似行为,但你的方案需要不同的值类型,请考虑实现自定义依赖属性。 你可以通过派生类中的类型转换或其他实现来链接属性值。

替代元数据的方案

替代现有依赖属性元数据的示例方案包括:

  • 更改默认值,这是一种常见方案。

  • 更改或添加属性更改回调,如果继承依赖属性与其他依赖属性的交互方式不同于其基本实现,则可能需要这样做。 支持代码和标记的编程模型的特征之一是可以按任意顺序设置属性值。 此因素会影响你实现属性更改回调的方式。 有关详细信息,请参阅依赖属性回调和验证

  • 更改 WPF 框架属性元数据选项。 通常,元数据选项是在注册新依赖属性期间设置的,但你可以在 OverrideMetadataAddOwner 调用中重新指定。 有关替代框架属性元数据的详细信息,请参阅指定 FrameworkPropertyMetadata。 有关如何在注册依赖属性时设置框架属性元数据选项的信息,请参阅自定义依赖属性

注意

由于验证回调不是元数据的一部分,因此无法通过替代元数据来更改它们。 有关详细信息,请参阅验证值回调

替代元数据

实现新的依赖属性时,可以使用 Register 方法的重载来设置其元数据。 如果类继承了依赖属性,你可以使用 OverrideMetadata 方法替代继承的元数据值。 例如,可以使用 OverrideMetadata 设置特定于类型的值。 有关详细信息和代码示例,请参阅替代依赖属性的元数据

Focusable 就是一个 WPF 依赖属性示例。 FrameworkElement 类注册 FocusableControl 类派生自 FrameworkElement,继承 Focusable 依赖属性,并替代继承属性元数据。 替代操作会将默认属性值从 false 更改为 true,但保留其他继承元数据值。

由于大多数现有依赖属性不是虚拟属性,因此其继承实现会隐藏现有成员。 替代元数据特征时,新的元数据值要么替换原始值,要么与之合并:

  • 对于 DefaultValue,新值将替换现有的默认值。 如果未在替代元数据中指定 DefaultValue,则该值来自在元数据中指定 DefaultValue 的最近上级。

  • 对于 PropertyChangedCallback,默认合并逻辑将所有 PropertyChangedCallback 值存储在一个表中,并在属性更改时调用所有值。 回调顺序取决于类深度,其中由层次结构中的基类注册的回调将首先运行。

  • 对于 CoerceValueCallback,新值将替换现有的 CoerceValueCallback 值。 如果未在替代元数据中指定 CoerceValueCallback,则该值来自在元数据中指定 CoerceValueCallback 的最近上级。

注意

默认合并逻辑由 Merge 方法实现。 你可以在继承依赖属性的派生类中指定自定义合并逻辑,方法是替代该类中的 Merge

将类添加为所有者

若要“继承”在不同类层次结构中注册的依赖属性,请使用 AddOwner 方法。 当添加类不是从注册依赖属性的类型派生时,通常使用此方法。 在 AddOwner 调用中,添加类可以为继承依赖属性创建和分配特定于类型的元数据。 若要通过代码和标记成为属性系统中的完整参与者,添加类应实现以下公共成员:

  • 依赖属性标识符字段。 依赖属性标识符的值是 AddOwner 调用的返回值。 此字段应该是 DependencyProperty 类型的 public static readonly 字段。

  • 实现 getset 访问器的 CLR 包装器。 通过使用属性包装器,依赖属性的使用者可以获取或设置依赖属性值,就像获取或设置任何其他 CLR 属性一样。 getset 访问器通过 DependencyObject.GetValueDependencyObject.SetValue 调用与底层属性系统交互,并以参数的形式传入依赖属性标识符。 以与注册自定义依赖属性时相同的方式实现包装器。 有关详细信息,请参阅自定义依赖属性

公开继承依赖属性的对象模型时,调用 AddOwner 的类与定义新自定义依赖属性的类具有相同的要求。 有关详细信息,请参阅添加依赖属性的所有者类型

附加属性元数据

在 WPF 中,WPF 类型上大多数与 UI 相关的附加属性都作为依赖属性实现。 作为依赖属性实现的附加属性支持依赖属性概念,例如派生类可以替代的元数据。 附加属性的元数据通常与依赖属性的元数据没有什么不同。 你可以在替代类的实例上替代继承附加属性的默认值、属性更改回调和 WPF 框架属性。 有关详细信息,请参阅附加属性元数据

注意

始终使用 RegisterAttached 来注册在元数据中指定 Inherits 的属性。 尽管属性值继承看起来对非附加依赖项属性有效,但通过运行时树中特定对象-对象划分的非附加属性的值继承行为并未定义。 Inherits 属性与非附加属性无关。 有关详细信息,请参阅 RegisterAttached(String, Type, Type, PropertyMetadata)Inherits 的备注部分。

添加类作为附加属性的所有者

若要从另一个类继承附加属性,但将其公开为你的类的非附加依赖属性:

  • 调用 AddOwner 以将你的类添加为附加依赖属性的所有者。

  • AddOwner 调用的返回值分配给 public static readonly 字段,以用作依赖属性标识符。

  • 定义 CLR 包装器,它将属性添加为类成员并支持非附加属性用法。

另请参阅