XAML 的标记扩展概述

标记扩展是用于获取值的 XAML 方法,该值既不是基元,也不是特定 XAML 类型。 用于特性时,标记扩展使用左大括号 { 的已知字符序列进入标记扩展范围,使用右大括号 } 退出。 使用 .NET Framework XAML 服务时,可以使用来自 System.Xaml 程序集的一些预定义的 XAML 语言标记扩展。 还可以从 System.Xaml 中定义的 MarkupExtension 类创建子类并定义您自己的标记扩展。 也可以使用由特定框架定义的标记扩展(如果您已引用该框架)。

访问标记扩展使用情况时,XAML 对象编写器可以通过 MarkupExtension.ProvideValue 重写中的服务连接点为自定义 MarkupExtension 类提供服务。 可以使用这些服务来获取有关使用情况的上下文、对象编写器的特定功能、XAML 架构上下文等。

本主题包括下列各节。

  • XAML 定义的标记扩展
  • MarkupExtension 基类
  • 为自定义标记扩展定义支持类型
  • 自定义标记扩展的构造函数模式和位置实参
  • 自定义标记扩展的命名实参
  • 通过标记扩展实现来访问服务提供程序上下文
  • 标记扩展的属性元素用法
  • 自定义标记扩展的特性化
  • 标记扩展用法的序列化
  • XAML 节点流中的标记扩展
  • 相关主题

XAML 定义的标记扩展

多种标记扩展是由 .NET Framework XAML 服务实现的,目的是获得 XAML 语言支持。 这些标记扩展对应于 XAML 语言的规范部分。 它们通常由语法中的 x: 前缀标识,如您在常见用法中所见到的一样。 这些 XAML 语言元素的 .NET Framework 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 标记扩展

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 标记扩展

其他 x: 构造

其他 x: 构造是为了支持存在的 XAML 语言功能,但这些构造并不作为标记扩展实现。 有关更多信息,请参见 XAML 命名空间 (x:) 语言功能

MarkupExtension 基类

若要定义可与 System.Xaml 中 XAML 读取器和 XAML 编写器的默认实现进行交互的自定义标记扩展,需要从抽象的 MarkupExtension 类派生一个类。 该类具有一个要重写的方法,即 ProvideValue。 可能还需要定义其他构造函数,以支持标记扩展用法的参数并与可设置的属性相匹配。

通过 ProvideValue,自定义标记扩展可以访问报告环境(其中标记扩展实际上由 XAML 处理器调用)的服务上下文。 在加载路径中,通常这是 XamlObjectWriter。 在保存路径中,通常这是 XamlXmlWriter。 每个报告环境的服务上下文都作为实现服务提供程序模式的内部 XAML 服务提供程序上下文类。 有关可用的服务以及它们所表示的内容的更多信息,请参见 XAML 的类型转换器和标记扩展

标记扩展类必须使用 Public 访问级别;XAML 处理器必须始终能够实例化标记扩展的支持类才能使用其服务。

为自定义标记扩展定义支持类型

使用在 .NET Framework XAML 服务上生成的 .NET Framework XAML 服务或框架时,可通过两种方法来命名标记扩展支持类型。 类型名称与 XAML 对象编写器在 XAML 中遇到标记扩展用法时尝试访问和调用标记扩展支持类型的方式有关。 使用以下命名策略之一:

  • 将类型名称命名为与 XAML 标记用法标记完全匹配。 例如,为了支持 {Collate ...} 扩展用法,将支持类型命名为 Collate。

  • 将类型名称命名为用法字符串标记加后缀 Extension。 例如,为了支持 {Collate ...} 扩展用法,将支持类型命名为 CollateExtension。

查找的顺序是先查找以 Extension 为后缀的类名,再查找没有 Extension 后缀的类名。

从标记用法的角度来看,将 Extension 后缀作为用法的一部分是有效的。 但是,此行为就如同 Extension 是类名的真正组成部分,并且如果支持类没有 Extension 后缀,则 XAML 对象编写器将无法解析该用法的标记扩展支持类。

默认构造函数

对于所有标记扩展支持类型,应公开一个公共的默认构造函数。 在 XAML 对象编写器实例化对象元素用法中的标记扩展的任何情况下,都需要使用默认构造函数。 支持标记扩展(尤其是序列化)的对象元素用法是一种合理预期。 但是,如果只想支持标记扩展的特性用法,则可以实现不带公共构造函数的标记扩展。

如果标记扩展用法没有实参,则需要默认构造函数来支持该用法。

自定义标记扩展的构造函数模式和位置实参

对于具有所需实参用法的标记扩展,公共构造函数必须与所需用法的模式相对应。 换句话说,如果您的标记扩展设计为需要一个位置实参作为有效用法,则应支持具有一个输入形参(它获取位置实参)的公共构造函数。

例如,假设 Collate 标记扩展仅用于支持一个模式,在该模式中有一个表示其模式的位置实参,被指定为 CollationMode 枚举常量。 在这种情况下,构造函数应具有以下形式:

public Collate(CollationMode collationMode) {...}

在基本级别,传递给标记扩展的参数是字符串,因为它们是从标记的特性值转发的。 可以使所有参数都成为字符串,并在该级别上使用输入。 但是,您有权访问在将标记扩展参数传递给支持类之前进行的某些处理。

从概念上讲,处理方式如同将标记扩展视为要创建的对象,然后设置其成员值。 计算要设置的每个指定属性,其计算方式类似于分析 XAML 时在创建的对象上设置指定成员的方式。 二者有两个重要的差异:

  • 如前所述,为了在 XAML 中进行实例化,标记扩展支持类型并不需要具有默认构造函数。 其对象构造会被推迟,直到对文本语法中的可能实参进行了标记化并以位置实参或命名实参的形式计算,并且此时会调用适当的构造函数。

  • 可以对标记扩展用法进行嵌套。 首先计算最内层的标记扩展。 因此,可以假定这样一种用法并将其中一个构造参数声明为需要生成值转换器(如标记扩展)的类型。

前面示例中演示了对此类处理的依赖性。 .NET Framework XAML 服务 XAML 对象编写器在本机级别将枚举常量名称处理成枚举值。

处理标记扩展位置形参的文本语法还可依赖于与构造实参中的类型相关联的类型转换器。

这些实参称为位置实参,因为用法中标记的出现顺序与为它们分配的构造函数形参的位置顺序相对应。 例如,请考虑以下构造函数签名:

public Collate(CollationMode collationMode, object collateThis) {...}

XAML 处理器希望此标记扩展有两个位置实参。 如果有用法 {Collate AlphaUp,{x:Reference circularFile}},则会将 AlphaUp 标记发送到第一个形参并以 CollationMode 枚举命名常量的形式进行计算。 会将内部 x:Reference 的结果发送给第二个形参并以对象的形式进行计算。

在标记扩展语法和处理的 XAML 指定规则中,逗号是实参之间的分隔符,而不管这些实参是位置实参还是命名实参。

位置参数的重复 Arity

如果 XAML 对象编写器遇到具有位置参数的标记扩展用法,并且存在多个带有此数目参数(重复 arity)的构造函数参数,这并不一定是错误。 该行为取决于可自定义的 XAML 架构上下文设置 SupportMarkupExtensionsWithDuplicateArity。 如果 SupportMarkupExtensionsWithDuplicateArity 为 true,则 XAML 对象编写器不应该仅由于重复 arity 的原因引发异常。 超出该点的行为并未严格定义。 基本设计假定架构上下文具有可用于特定参数的类型信息,并且可以尝试进行显式强制转换,从而匹配重复的候选项以查看哪个签名是最佳匹配。 如果没有任何签名可通过在 XAML 对象编写器上运行的该特定架构上下文施加的测试,则系统仍可能会引发异常。

默认情况下,在 .NET Framework XAML 服务的基于 CLR 的 XamlSchemaContext 中,SupportMarkupExtensionsWithDuplicateArity 为 false。 因此,如果遇到某个标记扩展用法(其中,后备类型的构造函数中存在重复 arity),则默认的 XamlObjectWriter 会引发异常。

自定义标记扩展的命名实参

由 XAML 指定的标记扩展还可对用法使用命名实参的形式。 在标记化的第一个级别,将文本语法分为多个实参。 任何实参中的等号 (=) 都将该实参标识为命名实参。 还会将这样一个实参标记化为名称/值对。 此情形中的名称将用于命名标记扩展支持类型的可公共设置的属性。 若要支持命名实参用法,则应该提供这些可公共设置的属性。 这些属性可以是继承的属性,只要它们仍保持为公共属性。

通过标记扩展实现来访问服务提供程序上下文

对于任何值转换器来说,可用的服务都相同。 差别是每个值转换器接收服务上下文的方式不同。 主题 XAML 的类型转换器和标记扩展中介绍了如何访问服务以及可用的服务。

标记扩展的属性元素用法

标记扩展用法的方案通常是围绕在特性用法中使用标记扩展而设计的。 但是,也可以定义后备类以支持属性元素用法。

为了支持标记扩展的属性元素用法,要定义一个公共的默认构造函数。 此函数应该是实例构造函数,而不是静态构造函数。 必须这样做,因为通常 XAML 处理器必须对它从标记中处理的任何对象元素调用默认构造函数,并且这包括作为对象元素的标记扩展类。 对于高级方案,可以为类定义非默认的构造路径。 (有关更多信息,请参见 x:FactoryMethod 指令。)但是,不应该将这些模式用于标记扩展目的,因为这无论对于设计人员还是对于原始标记的用户来说,都会显著加大他们发现用法模式的难度。

自定义标记扩展的特性化

为了支持设计环境和某些 XAML 对象编写器方案,应该使用多种 CLR 特性将标记扩展支持类型特性化。 这些特性报告所需的标记扩展用法。

MarkupExtensionReturnTypeAttributeProvideValue 返回的对象类型报告 Type 信息。 根据它的纯签名,ProvideValue 返回 Object。 但各使用方可能希望更精确的返回类型信息。 这包括:

  • 能够为标记扩展用法提供类型识别支持的设计器和 IDE。

  • 目标类上 SetMarkupExtension 处理程序的高级实现,它可能依赖于反射来确定标记扩展的返回类型,而不是按名称对特定的已知 MarkupExtension 实现进行分支。

标记扩展用法的序列化

当 XAML 对象编写器处理标记扩展用法并调用 ProvideValue 时,先前是其标记扩展用法的上下文将永久保留在 XAML 节点流中,而不是保留在对象图中。 在对象图中,只保留该值。 如果您拥有设计方案或有其他理由要将原始标记扩展用法永久保留于序列化的输出中,则必须设计您自己的基础结构以用于从加载路径 XAML 节点流跟踪标记扩展用法。 您可以实现以下行为:从加载路径重新创建节点流的元素,然后为 XAML 编写器播放以便在保存路径中进行序列化,从而取代节点流相应位置中的值。

XAML 节点流中的标记扩展

如果要在加载路径上使用 XAML 节点流,则会在节点流中以对象的形式出现标记扩展用法。

如果标记扩展用法使用位置参数,则将该标记扩展用法表示为具有初始化值的起始对象。 作为一个大致的文本表示形式,节点流类似于以下内容:

StartObject(XamlType 是标记扩展的定义类型,而不是它的返回类型)

  StartMember(XamlMember 的名称为 _InitializationText)

    Value(值是字符串形式的位置参数,包括其间的分隔符)

  EndMember

EndObject

具有命名参数的标记扩展用法表示为一个含有成员的对象,每个成员都具有以文本字符串值设置的相关名称。

调用标记扩展的 ProvideValue 实现实际上需要 XAML 架构上下文,因为它需要类型映射并创建标记扩展支持类型实例。 这是标记扩展用法以这种形式保留在默认 .NET Framework XAML 服务节点流中的一个原因,而加载路径的读取器部分通常没有可用的必要 XAML 架构上下文。

如果要在保存路径上使用 XAML 节点流,通常在对象图表示形式中不呈现任何内容,这会告诉您要序列化的对象最初是由某个标记扩展用法和 ProvideValue 结果提供的。 如果方案需要永久保留标记扩展用法,以便于往返同时也捕获对象图中的其他更改,则必须设计适于方案自己的方法,以保留来自原始 XAML 输入的标记扩展用法的知识。 例如,为了还原标记扩展用法,您可能需要在保存路径上使用节点流以便还原标记扩展用法,或在原始 XAML 和往返 XAML 之间执行某些类型的合并。 某些 XAML 实现框架(如 WPF)使用中间类型(表达式)来帮助表示标记扩展用法提供值的情况。

请参见

参考

MarkupExtension

概念

标记扩展和 WPF XAML

其他资源

XAML 的类型转换器和标记扩展