标记扩展是用于获取不是基元或特定 XAML 类型的值的 XAML 技术。 对于属性用法,标记扩展通过已知的左大括号 {
字符序列进入标记扩展范围,并通过右大括号 }
来退出。 使用 .NET XAML 服务时,可以使用 System.Xaml 程序集中的一些预定义 XAML 语言标记扩展。 还可以从定义在 System.Xaml 中的 MarkupExtension 类进行子类化,并定义自己的标记扩展。 或者,如果已引用该框架,则可以使用特定框架定义的标记扩展。
标记扩展用法被访问时,XAML 对象编写器可以通过MarkupExtension.ProvideValue重写中的服务连接点为自定义MarkupExtension类提供服务。 服务可用于获取有关用法的上下文、对象编写器的特定功能、XAML 架构上下文等。
XAML 定义的标记扩展
多个标记扩展由 .NET XAML 服务实现,以支持 XAML 语言。 这些标记扩展对应于 XAML 作为语言规范的一部分。 这些通常可以通过 x:
语法中的前缀来识别,在常见的使用中可以看到这种情况。 这些 XAML 语言元素的 .NET XAML 服务实现都派生自 MarkupExtension 基类。
注释
该 x:
前缀用于 XAML 生产根元素中 XAML 语言命名空间的典型 XAML 命名空间映射。 例如,各种特定框架的 Visual Studio 项目和页面模板使用此 x:
映射启动 XAML 文件。 可以在自己的 XAML 命名空间映射中选择不同的前缀标记,但本文档将假定默认 x:
映射是标识作为 XAML 语言 XAML 命名空间定义部分的实体,而不是特定框架的默认 XAML 命名空间或其他任意 CLR 或 XML 命名空间。
x:Type
x:Type
提供 Type 命名类型的对象。 此功能最常用于延期机制,这些机制使用基本 CLR 类型和类型派生作为分组名称或标识符。 WPF 样式和模板及其属性用法 TargetType
是一个特定示例。 有关详细信息,请参阅 x:Type 标记扩展。
x:Static
x:Static
从值类型代码实体中生成静态值,这些实体不是直接属性值的类型,但可以计算为该类型。 这可用于指定类型定义中已经存在为已知常量的值。 有关详细信息,请参阅 x:Static Markup Extension。
x:Null
x:Null
指定 null
为 XAML 成员的值。 根据特定类型或大型框架概念的设计, null
并非始终是属性的默认值,也不是空字符串属性的隐含值。 有关详细信息,请参阅 x:Null 标记扩展。
x:Array
x:Array
支持在 XAML 语法中创建常规数组,在这种情况下,故意不使用基元素和控件模型提供的集合支持。 有关详细信息,请参阅 x:Array 标记扩展。 具体而言,在 XAML 2009 中,数组作为语言基元而不是扩展进行访问。 有关详细信息,请参阅 XAML 2009 语言功能。
x:Reference
x:Reference
是 XAML 2009 的一部分,它是原始 (2006) 语言集的扩展。
x:Reference
表示对对象图中另一个现有对象的引用。 该对象由其 x:Name
标识。 有关详细信息,请参阅 x:Reference Markup Extension。
其他 x 构造
支持 XAML 语言功能的其他 x:
构造存在,但这些构造不是作为标记扩展实现的。 有关详细信息,请参阅 XAML 命名空间 (x:) 语言功能。
MarkupExtension 基类
若要定义可与 System.Xaml 中 XAML 读取器和 XAML 编写器的默认实现交互的自定义标记扩展,请从抽象 MarkupExtension 类派生类。 该类有唯一一个可以重写的方法,即 ProvideValue. 可能还需要定义其他构造函数,以支持在使用标记扩展时的参数,并确保这些参数匹配可设置的属性。
通过 ProvideValue自定义标记扩展可以访问服务上下文,该上下文报告 XAML 处理器调用标记扩展的环境。 在加载路径中,这通常是一个 XamlObjectWriter。 在保存路径中,这通常是一个 XamlXmlWriter。 每个报告服务上下文作为实现服务提供程序模式的内部 XAML 服务提供程序上下文类。 有关可用服务及其表示内容的详细信息,请参阅 XAML 的类型转换器和标记扩展。
标记扩展类必须使用公共访问级别;XAML 处理器必须始终能够实例化标记扩展的支持类才能使用其服务。
定义自定义标记扩展的支持类型
使用基于 .NET XAML 服务的 .NET XAML 服务或框架时,有两种选择来命名标记扩展支持类型。 类型名称与 XAML 对象编写器在 XAML 中遇到标记扩展用法时如何尝试访问和调用标记扩展支持类型相关。 使用以下命名策略之一:
- 将类型名称设置为与 XAML 标记中的使用令牌完全匹配。 例如,若要支持
{Collate ...}
扩展用法,请命名支持类型Collate
。 - 将类型名称命名为用法字符串标记加上后缀
Extension
。 例如,若要支持{Collate ...}
扩展用法,请命名支持类型CollateExtension
。
查找顺序是先查找 Extension
后缀类名,然后查找没有后缀的 Extension
类名。
从标记使用的角度来看,将 Extension
后缀作为用法的一部分是有效的。 但是,这的行为就像 Extension
是类名的真正一部分一样,如果支持类没有后缀,XAML 对象编写器将无法解析该用法的 Extension
标记扩展支持类。
无参数构造函数
对于所有标记扩展支持类型,您应公开一个公共无参数构造函数。 如果 XAML 对象编写器需要通过对象元素用法来实例化标记扩展,则需要一个无参数构造函数。 支持对象元素的使用是对标记扩展的合理期望,尤其在序列化时。 但是,如果只想支持标记扩展的属性用法,则可以在没有公共构造函数的情况下实现标记扩展。
如果标记扩展用法没有参数,则需要无参数构造函数来支持使用。
自定义标记扩展的构造函数模式和位置参数
对于具有预期参数用法的标记扩展,公共构造函数必须与预期用法的模式相对应。 换句话说,如果标记扩展旨在要求一个位置参数作为有效用法,则应支持具有一个采用位置参数的输入参数的公共构造函数。
例如,假设 Collate
标记扩展只支持一个模式,其中有一个位置参数表示其模式,指定为 CollationMode
枚举常量。 在这种情况下,应有具有以下形式的构造函数:
public Collate(CollationMode collationMode) {...}
在基本级别,传递给标记扩展的参数是一个字符串,因为它们正从标记的属性值转发。 可以将所有参数转换为字符串,并在该层级处理输入。 但是,您确实有权访问在将标记扩展参数传递给支持类之前发生的某些处理。
从概念上来说,处理方式就如同创建一个标记扩展对象,然后设置其成员值。 将评估要设置的每个指定属性,类似于在分析 XAML 时如何在创建的对象上设置指定的成员。 有两个重要区别:
- 如前所述,标记扩展支持类型不需要具有无参数构造函数才能在 XAML 中实例化。 其对象构造将推迟到文本语法中的可能参数被标记化并计算为位置或命名参数,并在该时间调用相应的构造函数。
- 标记扩展的用法可以被嵌套使用。 首先评估最内部的标记扩展。 因此,可以假设使用此类用法,并将其中一个构造参数声明为需要值转换器(如标记扩展)才能生成的类型。
上一示例中显示了对此类处理的依赖。 .NET XAML 服务中的 XAML 对象编写器将枚举常量名称转换为本机级别的枚举值。
处理标记扩展位置参数的文本语法还可以依赖于与构造参数中的类型关联的类型转换器。
参数称为位置参数,因为使用中的标记的遇到顺序对应于为其分配的构造函数参数的位置顺序。 例如,请考虑构造函数签名的以下示例:
public Collate(CollationMode collationMode, object collateThis) {...}
XAML 处理器需要此标记扩展的两个位置参数。 如果存在使用 {Collate AlphaUp,{x:Reference circularFile}}
,则将 AlphaUp
令牌发送到第一个参数,并将其评估为名为 CollationMode
的枚举常量。 内部结果 x:Reference
被发送到第二个参数,并被评估为对象。
在 XAML 指定的标记扩展语法和处理规则中,逗号是参数之间的分隔符,无论这些参数是位置参数还是命名参数。
参数个数重复的现象
如果 XAML 对象编写器遇到带有位置参数的标记扩展用法,并且有多个构造函数参数采用该数目的参数(重复的 arity),这不一定是错误。 此行为取决于可自定义的 XAML 架构上下文设置 SupportMarkupExtensionsWithDuplicateArity。 如果SupportMarkupExtensionsWithDuplicateArity是true
,那么 XAML 对象编写器不应仅因重复复数而引发异常。 超出该点的行为未严格定义。 基本设计假设是,架构上下文具有可用于特定参数的类型信息,并且可以尝试显式类型转换,以匹配重复的候选项,从而确定哪个签名可能是最佳匹配项。 如果没有签名能够通过正在 XAML 对象编写器上运行的特定架构上下文的测试,则仍可能会抛出异常。
默认情况下,SupportMarkupExtensionsWithDuplicateArity 是 CLR 基于 XamlSchemaContext 的 .NET XAML 服务中的 false
。 因此,如果遇到标记扩展用法,则默认值 XamlObjectWriter 会引发异常,因为后盾类型的构造函数存在重复的 arity。
自定义标记扩展的命名参数
XAML 指定的标记扩展还可以使用命名参数表单进行使用。 在第一个标记化级别,文本语法分为参数。 任何参数中的等号 (=) 的存在将参数标识为命名参数。 此类参数也标记为名称/值对。 在这个例子中,名称表示标记扩展中支持类型的一个可公开设置的属性。 如果想要支持命名参数用法,则应提供这些公共可设置的属性。 只要这些属性是公开的,它们就可以继承。
从标记扩展实现中访问服务提供者上下文
对于任何值转换器,可用的服务都是相同的。 区别在于每个值转换器接收服务上下文的方式。 访问服务和可用服务已在 XAML 的主题 类型转换器和标记扩展中记录。
标记扩展的属性元素用法
标记扩展用法的方案通常围绕在属性用法中使用标记扩展进行设计。 但是,还可以定义支持属性元素用法的后盾类。
若要支持标记扩展的属性元素用法,请定义公共无参数构造函数。 这应该是实例构造函数,而不是静态构造函数。 这是必需的,因为 XAML 处理器通常必须在它从标记处理的任何对象元素上调用无参数构造函数,并且这包括将标记扩展类作为对象元素。 对于高级方案,可以为类定义非默认构造路径。 (有关详细信息,请参阅 x:FactoryMethod 指令。)但是,不应将这些模式用于标记扩展目的,因为这会使发现使用模式变得更加困难,无论是对于设计器还是原始标记的用户。
自定义标记扩展的引用
为了支持设计环境和某些 XAML 对象编写器场景,您应该使用几个 CLR 属性来标记扩展支持类型。 这些属性报告预期的标记扩展用法。
MarkupExtensionReturnTypeAttribute报告与ProvideValue返回的对象类型相关的Type信息。 ProvideValue 按其纯签名返回 Object。 但各种使用者可能想要更精确的返回类型信息。 这包括:
- 设计器和IDE或许能够为标记扩展的用法提供类型感知支持。
- 目标类上处理程序的高级实现
SetMarkupExtension
,这些处理程序可能依赖于反射来确定标记扩展的返回类型,而不是按名称对特定已知 MarkupExtension 实现进行分支。
标记扩展用法的序列化
当 XAML 对象编写器处理标记扩展用法和调用 ProvideValue时,以前作为标记扩展用法的上下文将保留在 XAML 节点流中,但不保留在对象图中。 在对象图中,仅保留值。 如果你有将原始标记扩展用法保存到序列化输出中的设计方案或其他原因,则必须设计自己的基础结构,以便从加载路径 XAML 节点流跟踪标记扩展用法。 你可以实现行为,从加载路径重新创建节点流的元素,并将其播放回 XAML 编写器,以便在保存路径中序列化,替换节点流的适当位置中的值。
XAML 节点流中的标记扩展
如果在加载路径上使用 XAML 节点流,则标记扩展用法将作为对象显示在节点流中。
如果标记扩展用法使用位置参数,则表示为具有初始化值的起始对象。 作为粗略的文本表示形式,节点流如下所示:
StartObject
(XamlType 是标记扩展的定义类型,而不是其返回类型)
StartMember
(名称 XamlMember 为 _InitializationText
)
Value
(值是作为字符串的位置参数,包括干预分隔符)
EndMember
EndObject
带有命名参数的标记扩展用法表示为具有相关名称成员的对象,每个对象都带有文本字符串值。
实际上,调用 ProvideValue
标记扩展的实现需要 XAML 架构上下文,因为这需要类型映射和创建标记扩展支持类型实例。 这是在默认 .NET XAML 服务节点流中以这种方式保留标记扩展用法的原因之一 - 加载路径的读取器部分通常没有必要的 XAML 架构上下文。
当在保存路径上处理 XAML 节点流时,通常没有任何信息能够表明要序列化的对象最初是通过标记扩展使用或 ProvideValue
结果提供的。 需要保留往返标记扩展用法,同时捕获对象图中的其他更改的方案必须设计自己的技术,以保留原始 XAML 输入中的标记扩展用法的知识。 例如,若要还原标记扩展用法,可能需要使用保存路径上的节点流来还原标记扩展用法,或执行原始 XAML 与圆角 XAML 之间的某种类型的合并。 某些 XAML 实现框架(如 WPF)使用中间类型(表达式)来帮助表示标记扩展用法提供值的情况。
另请参阅
- MarkupExtension
- 适用于 XAML 的
类型转换器和标记扩展 - 标记扩展和 WPF XAML