依赖属性元数据
Windows Presentation Foundation (WPF) 属性系统包括一个元数据报告系统,该系统不局限于可以通过反射或常规公共语言运行时 (CLR) 特征报告的关于某个属性的内容。 依赖属性的元数据还可以由定义依赖属性的类来唯一地分配,可以在依赖属性添加到另一个类时进行更改,可以由所有从定义基类继承依赖属性的派生类来明确地重写。
先决条件
本主题假定你作为 WPF 类的现有依赖属性的使用者已经对依赖属性有所了解,并且已经阅读依赖属性概述。 为了能理解本主题中的示例,还应了解 XAML 并知道如何编写 WPF 应用程序。
依赖属性元数据的使用方式
依赖属性的元数据作为一个对象存在,可以通过查询该对象来检查依赖属性的特征。 当属性系统处理任何给定的依赖属性时,也会经常访问这些元数据。 依赖属性的元数据对象可以包含以下类型的信息:
依赖属性的默认值(如果通过本地值、样式和继承等信息不能确定依赖属性的其他任何值)。有关在为依赖属性赋值时,默认值如何参与属性系统所使用的优先级的完整讨论,请参阅依赖属性值优先级。
对影响每个所有者类型的强制行为或更改通知行为的回叫实现的引用。 请注意,这些回叫通常是用非公共访问级别定义的,因此,除非实际引用位于允许的访问范围内,否则通常无法从元数据获得这些引用。 有关依赖属性回叫的详细信息,请参阅依赖属性回调和验证。
如果所讨论的依赖属性被视为一个 WPF 框架级别的属性,则元数据中可能包含 WPF 框架级别的依赖属性特征,这些特征报告各种服务(如 WPF 框架级别的布局引擎和属性继承逻辑)的信息和状态。 有关依赖属性元数据的这一方面的详细信息,请参阅框架属性元数据。
元数据 API
可报告属性系统所使用的大部分元数据信息的类型是 PropertyMetadata 类。 在向属性系统注册依赖属性时,可以选择指定元数据实例,并且可以为以下附加类型再次指定这些实例:将自身作为所有者添加的类型,或者重写它们从基类依赖属性定义继承的元数据的类型。 (如果在注册属性时未指定元数据,则使用该类的默认值来创建默认 PropertyMetadata。)当针对 DependencyObject 实例调用各种从依赖属性获取元数据的 GetMetadata 重载时,所注册的元数据作为 PropertyMetadata 返回。
随后会从 PropertyMetadata 类派生,以便为体系结构区域(如 WPF 框架级别的类)提供更具体的元数据。 UIPropertyMetadata 添加动画报告功能,FrameworkPropertyMetadata 提供上一节中提到的 WPF 框架级别的属性。 在注册依赖属性之后,可以将它们注册到这些 PropertyMetadata 派生类中。 在检查元数据之后,可以选择将 PropertyMetadata 基类型强制转换为派生类,这样就可以检查更具体的属性。
注意
在本文档中,有时将可以在 FrameworkPropertyMetadata 中指定的属性特征称为“标志”。 在新建要用于注册依赖属性或重写元数据的元数据实例时,将使用按标志枚举 FrameworkPropertyMetadataOptions 来指定这些值,然后向 FrameworkPropertyMetadata 构造函数提供枚举的可能连接值。 但是,一经构造,这些选项特征就会在 FrameworkPropertyMetadata 中作为一系列布尔属性而不是构造枚举值公开。 使用布尔属性,可以检查每个条件,而不必为了获得感兴趣的信息而向按标志枚举值应用掩码。 构造函数使用连接的 FrameworkPropertyMetadataOptions 使构造函数签名保持合理的长度,而实际构造的元数据公开不同的属性,使元数据的查询变得更加直观。
何时重写元数据以及何时派生类
WPF 属性系统已经建立了如下功能:在不必完全重新实现依赖属性的情况下,更改依赖属性的某些特征。 这是通过为特定类型上所存在的依赖属性构造不同的属性元数据实例来完成的。 请注意,现有的大多数依赖属性都不是虚拟属性,因此,严格地说,只能通过隐藏现有成员来针对继承类“重新实现”依赖属性。
如果尝试对某个类型的依赖属性启用的方案不能通过修改现有依赖属性的特征来完成,则可能有必要创建一个派生类,然后为该派生类声明一个自定义依赖属性。 自定义依赖属性与 WPF API 定义的依赖属性具有相同的行为。 有关自定义依赖属性的更多详细信息,请参阅自定义依赖属性。
不能重写的依赖属性的一个显著特征就是它的值类型。 如果要继承的依赖属性的行为与所需的行为大体相同,但是要求它具有另一种类型,则必须实现一个自定义依赖属性,可能还需要通过类型转换或其他实现机制在自定义类上链接这些属性。 而且,不能替换现有的 ValidateValueCallback,因为此回叫存在于注册字段本身,而不是存在于它的元数据中。
更改现有元数据的方案
如果要处理现有依赖属性的元数据,则更改依赖属性元数据的一种常见方案是更改默认值。 更改或添加属性系统回叫是一种更高级的方案。 如果所实现的派生类的依赖属性之间具有不同的相互关系,则你可能希望这样做。 让编程模型既支持代码又支持声明性用法的条件之一就是,属性必须能够按任何顺序设置。 因此,需要在没有上下文的情况下实时设置任何依赖属性,而且可以不必知道设置顺序(例如,可能在构造函数中找到的顺序)。 有关属性系统这一方面的详细信息,请参阅依赖属性回调和验证。 请注意,验证回叫不是元数据的一部分,而是依赖属性标识符的一部分。 因此,不能通过重写元数据来更改验证回叫。
在某些情况下,可能还希望在现有的依赖属性上改变 WPF 框架级别的属性元数据选项。 这些选项将有关 WPF 框架级别属性的某些已知条件传递到其他 WPF 框架级别的进程,例如布局系统。 通常,只有在注册新的依赖属性时,才设置这些选项,但是也可以在调用 OverrideMetadata 或 AddOwner 的过程中更改 WPF 框架级别属性的元数据。 有关要使用的特定值以及详细信息,请参阅框架属性元数据。 有关应当如何为新注册的依赖属性设置这些选项的详细信息,请参阅自定义依赖属性。
重写元数据
重写元数据的主要目的在于,使你有机会更改各种派生自元数据的行为,这些行为应用于类型上存在的依赖属性。 元数据一节中更详细地介绍了重写元数据的原因。 有关详细信息(包括一些代码示例),请参阅重写依赖属性的元数据。
在注册调用 (Register) 过程中可以为依赖属性提供属性元数据。 但是,在许多情况下,当类继承该依赖属性时,你可能希望为该类提供特定于类型的元数据。 为此,可调用 OverrideMetadata 方法。 对于 WPF API 中的示例,FrameworkElement 类是第一个注册 Focusable 依赖属性的类型。 但是,Control 类会重写该依赖属性的元数据以提供自己的初始默认值,将它从 false
改为 true
,并以其他方式重用最初的 Focusable 实现。
当你重写元数据时,系统会合并或替换不同的元数据特征。
会合并 PropertyChangedCallback。 如果添加一个新的 PropertyChangedCallback,该回叫则存储在元数据中。 如果没有在替代中指定 PropertyChangedCallback,PropertyChangedCallback 的值则会从在元数据中指定它的最近上级提升为一个引用。
PropertyChangedCallback 的实际属性系统行为是:层次结构中所有元数据所有者的实现都保留并添加到表中,属性系统的执行顺序是首先调用最深派生类的回叫。
会替换 DefaultValue。 如果没有在替代中指定 DefaultValue,DefaultValue 的值则来自在元数据中指定它的最近上级。
会替换 CoerceValueCallback 实现。 如果添加一个新的 CoerceValueCallback,该回叫则存储在元数据中。 如果没有在替代中指定 CoerceValueCallback,CoerceValueCallback 的值则会从在元数据中指定它的最近上级提升为一个引用。
属性系统的行为是仅调用直接元数据中的 CoerceValueCallback。 不保留对层次结构中其他 CoerceValueCallback 实现的引用。
此行为由 Merge 实现,并且可以在派生的元数据类上替代。
重写附加属性元数据
在 WPF 中,附加属性作为依赖属性来实现。 这意味着它们还具有能够由个别类重写的属性元数据。 关于 WPF 中的附加属性的范围,通常需要注意:可以针对任何 DependencyObject 设置附加属性。 因此,任何 DependencyObject 派生类都可以重写任何附加属性的元数据,就好像它们是在类的实例上设置的一样。 可以重写默认值、回叫或 WPF 框架级别的特征报告属性。 如果针对类的实例设置了附加属性,则这些重写属性元数据特征将适用。 例如,可以重写默认值,这样,只要未以其他方式设置附加属性,重写值就会报告为类实例的附加属性的值。
注意
Inherits 属性与附加属性无关。
将类作为现有依赖属性的所有者来添加
通过使用 AddOwner 方法,类可以将自身作为已注册的依赖属性的所有者来添加。 这使得该类可以使用最初针对另一个类型注册的依赖属性。 添加类通常不是首先将该依赖属性注册为所有者的类型的派生类。 实际上,这使类及其派生类可以“继承”依赖属性实现,而不需要最初的所有者类,而且添加类也不必位于同一个实际的类层次结构中。 另外,添加类(以及所有派生类)随后可以为最初的依赖属性提供特定于类型的元数据。
添加类除了通过属性系统的实用工具方法将自身添加为所有者以外,还应当在自身声明其他公共成员,以使依赖属性向代码和标记公开,从而完全参与属性系统。 就为依赖属性公开对象模型而言,添加现有依赖属性的类与定义新的自定义依赖属性的类具有相同的职责。 要公开的第一个此类成员是依赖属性标识符字段。 此字段应当是类型为 DependencyProperty 的 public static readonly
字段,此类型将赋予 AddOwner 调用的返回值。 要定义的第二个成员是公共语言运行时 (CLR)“包装器”属性。 使用包装器,可以更方便地在代码中操作依赖属性(应当避免每次都调用 SetValue,而且在包装器本身只能发出该调用一次)。 包装器的实现方式与在注册自定义依赖属性时的实现方式完全相同。 有关实现依赖属性的详细信息,请参阅自定义依赖属性和为依赖属性添加所有者类型。
AddOwner 和附加属性
对于由所有者类定义为附加属性的依赖属性,可以调用 AddOwner。 这样做的目的通常是为了将以前附加的属性作为非附加依赖属性来公开。 随后将 AddOwner 返回值作为一个要用作依赖属性标识符的 public static readonly
字段来公开,并定义相应的“包装器”属性,以便该属性出现在成员表中并支持在类中使用非附加属性。