依赖属性元数据
Windows Presentation Foundation (WPF) 属性系统包括一个元数据报告系统,它能够报告关于属性的信息超越了通过反射或通用公共语言运行时 (CLR) 特征所能达到的范围。 依赖属性的元数据也可以由定义依赖属性的类唯一分配,在将依赖属性添加到其他类时可以更改,并且可由从定义基类继承依赖属性的所有派生类专门重写。
先决条件
本主题假定你从 WPF 类上现有依赖属性使用者的角度了解依赖属性,并已阅读 依赖属性概述。 为了遵循本主题中的示例,还应了解 XAML 以及如何编写 WPF 应用程序。
如何使用依赖项属性元数据
依赖属性元数据作为一个对象存在,可以查询这些对象来检查依赖属性的特征。 属性系统也会经常访问此元数据,因为它处理任何给定的依赖属性。 依赖属性的元数据对象可以包含以下类型的信息:
依赖属性的默认值,如果无法通过本地值、样式、继承等确定依赖属性的其他值。有关在为依赖属性分配值时,默认值如何参与属性系统使用的优先级的深入讨论,请参阅 依赖属性值优先级。
对影响按所有者类型强制或更改通知行为的回调实现的引用。 请注意,这些回调通常使用非公共访问级别定义,因此通常不可能从元数据获取实际引用,除非引用在允许的访问范围内。 有关依赖属性回调的详细信息,请参阅 依赖属性回调和验证。
如果相关的依赖属性被视为 WPF 框架级属性,则元数据可能包含 WPF 框架级依赖属性特征,这些特征报告了 WPF 框架级布局引擎和属性继承逻辑等服务的信息和状态。 有关依赖属性元数据这一方面的详细信息,请参阅 Framework 属性元数据。
元数据接口
提供属性系统使用的大多数元数据信息的类型是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 框架级属性元数据。 有关要使用的特定值和详细信息,请参阅 Framework 属性元数据。 有关如何为新注册的依赖属性设置这些选项的详细信息,请参阅 自定义依赖属性。
重写元数据
重写元数据主要是为了让你有机会更改依赖属性在该类型中存在时所应用的各种元数据派生行为。 元数据 部分中更详细地解释了这一点的原因。 有关详细信息(包括一些代码示例),请参阅 重写依赖属性的元数据。
可以在注册调用(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 方法将自身添加为已注册的依赖属性的所有者。 这样,该类就能够使用最初为其他类型注册的依赖属性。 添加的类通常不是首次将依赖属性注册为所有者的那种类型的派生类。 实际上,这允许您的类及其派生类在原始所有者类和添加类不在同一个真正的类层次结构中时“继承”依赖属性实现。 此外,添加类(以及所有派生类)可以为原始依赖属性提供特定于类型的元数据。
除了通过属性系统实用工具方法将自身添加为所有者,添加类还应在自身上声明其他公共成员,以使依赖属性成为属性系统中具有公开代码和标记的完整参与者。 添加现有依赖属性的类具有与公开该依赖属性的对象模型相同的责任,就像定义新的自定义依赖属性的类一样。 要公开的第一个此类成员是依赖属性标识符字段。 此字段应为 public static readonly
字段,其类型为 DependencyProperty,并分配给 AddOwner 调用的返回值。 要定义的第二个成员是公共语言运行时(CLR)“包装器”属性。 包装器可以更方便地在代码中操作依赖项属性(避免每次调用 SetValue,并且只能在包装器本身中调用一次)。 包装器的实现方式与如果要注册自定义依赖属性时的实现方式相同。 有关实现依赖属性的详细信息,请参阅 自定义依赖属性 和 添加依赖属性的所有者类型。
AddOwner 和附加属性
可以调用 AddOwner 来获取由所有者类定义为附加属性的依赖属性。 一般情况下,这样做的原因是将以前附加的属性公开为非附加依赖属性。 然后,将 AddOwner 返回值公开为用作依赖属性标识符的 public static readonly
字段,并定义适当的“包装器”属性,使属性显示在成员表中,并支持类中的非附加属性用法。