标记扩展与 WPF XAML
本主题介绍 XAML 的标记扩展概念,包括其语法规则、用途以及它们所依据的类对象模型。 标记扩展是 XAML 语言和 XAML 服务的 .NET 实现的一般功能。 本主题专门详细介绍了在 WPF XAML 中使用的标记扩展。
XAML 处理器和标记扩展
一般来说,XAML 分析程序可以将属性值解释为可以转换为基元的文本字符串,也可以通过某种方式将其转换为对象。 一种这样的方式是引用类型转换器;本主题 TypeConverters 和 XAML中记录了这一点。 但是,在某些情况下,需要不同的行为。 例如,可以指示 XAML 处理器属性的值不应导致对象图中的新对象。 相反,该特性应生成一个对象图,该图引用图形的另一部分或静态对象中已构造的对象。 另一种情况是,可以指示 XAML 处理器使用向对象的构造函数提供非默认参数的语法。 这些是标记扩展可以提供解决方案的方案类型。
基本标记扩展语法
可以实现标记扩展,以便为属性用法中的属性、属性元素用法中的属性或两者提供值。
用于提供属性值时,XAML 处理器通过语法中出现的左大括号和右大括号({ 和 })来识别标记扩展序列。 然后,标记扩展的类型由紧跟在左花括号后面的字符串标记符标识。
在属性元素语法中使用时,标记扩展在视觉上与用于提供属性元素值的任何其他元素相同:将标记扩展类引用为元素的 XAML 元素声明(括在尖括号内(<>)。
XAML-Defined 标记扩展
存在多个标记扩展,这些扩展不是 XAML 在 WPF 中的特定实现,而是 XAML 作为一种语言的内在功能或特性的实现。 这些标记扩展在 System.Xaml 程序集中作为常规 .NET Framework XAML 服务的一部分实现,并且位于 XAML 语言 XAML 命名空间中。 在常见标记用法方面,这些标记扩展通常由用法中的 x:
前缀标识。 MarkupExtension 基类(也在 System.Xaml 中定义)提供了所有标记扩展都应使用的模式,以便在 XAML 读取器和 XAML 编写器(包括 WPF XAML 中)中受支持。
x:Type
为命名类型提供 Type 对象。 此功能最常用于样式和模板。 有关详细信息,请参阅 x:Type 标记扩展。x:Static
生成静态值。 这些值来自值类型代码对象,这些对象本身并不是目标属性值的类型,但可以评估为该类型。 有关详细信息,请参阅 x:Static Markup Extension。x:Null
将null
指定为属性的值,并可用于属性或属性元素值。 有关详细信息,请参阅 x:Null 标记扩展。x:Array
支持在 XAML 语法中创建常规数组,用于有意不使用 WPF 基元素和控件模型提供的集合支持的情况。 有关详细信息,请参阅 x:Array 标记扩展。
注意
x:
前缀用于 XAML 文件或生产元素中 XAML 语言内部函数的典型 XAML 命名空间映射。 例如,WPF 应用程序的 Visual Studio 模板使用此 x:
映射启动 XAML 文件。 可以在自己的 XAML 命名空间映射中选择不同的前缀标记,但本文档将假定默认 x:
映射作为标识 XAML 语言 XAML 命名空间中定义的部分实体,而不是 WPF 默认命名空间或其他与特定框架无关的 XAML 命名空间。
WPF-Specific 标记扩展
WPF 编程中使用的最常见标记扩展是支持资源引用(StaticResource
和 DynamicResource
)和支持数据绑定的标记扩展(Binding
)。
StaticResource
通过替换已定义资源的值来为属性提供值。 最终在加载 XAML 时进行StaticResource
评估,并且在运行时无法访问对象图。 有关详细信息,请参阅 StaticResource 标记扩展。DynamicResource
通过将属性值延迟为一个资源的运行时引用来提供该值。 动态资源引用在每次访问该资源时都会强制进行新的查找,并且在运行时可以访问对象图。 为了获取此访问权限,DynamicResource
概念由 WPF 属性系统中的依赖属性以及计算表达式提供支持。 因此,只能对依赖属性目标使用DynamicResource
。 有关详细信息,请参阅 DynamicResource 标记扩展。Binding
使用在运行时应用于父对象的数据上下文为属性提供数据绑定值。 此标记扩展相对复杂,因为它可实现大量内联语法来指定数据绑定。 有关详细信息,请参阅 绑定标记扩展。RelativeSource
提供 Binding 的源信息,该信息可在运行时对象树中导航多个可能的关系。 这为在多用途模板中创建的绑定,或在不完全了解周围对象树的情况下在代码中创建的绑定,提供专门的资源支持。 有关详细信息,请参阅 RelativeSource MarkupExtension。TemplateBinding
使控件模板能够使用模板化属性的值,这些属性来自将使用模板的类的对象模型定义的属性。 换句话说,模板定义中的属性可以访问仅应用模板后才存在的上下文。 有关详细信息,请参阅 TemplateBinding 标记扩展。 有关实际使用TemplateBinding
的详细信息,请参阅 使用 ControlTemplates 进行样式设置的示例。ColorConvertedBitmap
支持相对高级的成像场景。 有关详细信息,请参阅 ColorConvertedBitmap 标记扩展。ComponentResourceKey
和ThemeDictionary
在资源查找方面提供支持,特别是对与自定义控件一起打包的资源和主题。 有关详细信息,请参阅 ComponentResourceKey 标记扩展、ThemeDictionary 标记扩展或 控件创作概述。
*扩展类
对于常规 XAML 语言和特定于 WPF 的标记扩展,每个标记扩展的行为都通过派生自 MarkupExtension的 *Extension
类向 XAML 处理器标识,并提供 ProvideValue 方法的实现。 每个扩展上的此方法都提供计算标记扩展时返回的对象。 返回的对象通常基于传递给标记扩展的各种字符串标记符进行评估。
例如,StaticResourceExtension 类提供实际资源查找的表面实现,使得其 ProvideValue 实现能够返回请求的对象,而该特定实现的输入是用于通过其 x:Key
查找资源的字符串。 如果使用的是现有标记扩展,此实现详细信息的大部分内容并不重要。
某些标记扩展不使用字符串标记参数。 这是因为它们返回静态值或一致值,或者因为应返回的值的上下文可通过通过 serviceProvider
参数传递的服务之一获得。
*Extension
命名模式是为了方便和一致性。 XAML 处理器不必将该类标识为对标记扩展的支持。 只要你的代码库包括 System.Xaml 并使用 .NET Framework XAML 服务实现,要被识别为 XAML 标记扩展,你只需从 MarkupExtension 派生并支持构造语法。 WPF 定义不遵循 *Extension
命名模式的标记扩展启用类,例如 Binding。 通常,这是因为类支持的不仅仅是纯标记扩展的场景。 对于 Binding,这个类支持在运行时访问与 XAML 无关的对象方法和属性。
初始化文本的扩展类解释
标记扩展名称后面的字符串标记,并且仍位于大括号内,可通过以下方式之一由 XAML 处理器解释:
逗号始终表示各个符号的分隔符或界定符。
如果单独的分隔标记不包含任何等号,则每个令牌被视为构造函数参数。 每个构造函数参数都必须按照该签名所要求的类型提供,并且以该签名所要求的顺序提供。
注意
XAML 处理器必须调用与成对数量的参数计数匹配的构造函数。 因此,如果要实现自定义标记扩展,请不要提供具有相同参数计数的多个构造函数。 如果存在具有相同参数计数的多个标记扩展构造函数路径,其行为未被定义,但应预见到,XAML 处理器可能允许在此情况下,在使用时引发异常。
如果单独的分隔标记包含等号,则 XAML 处理器首先调用标记扩展的无参数构造函数。 然后,每个 name=value 对应被视为标记扩展中存在的属性名称,以及要分配给该属性的值。
如果构造函数行为与标记扩展中的属性设置行为之间存在并行结果,则使用哪种行为无关紧要。 在具有多个可设置属性的标记扩展中,更常使用属性
=
值 对,这不仅因为它能使标记更有意图,而且还能减少你意外置换构造函数参数的可能性。 (指定属性=值对时,这些属性可能按任意顺序排列。)此外,不能保证标记扩展提供一个构造函数的参数来设置它的每一个可设置的属性。 例如,Binding 是一个标记扩展,其中许多属性可以通过 属性=
值 形式进行设置,但 Binding 仅支持两个构造函数:一个无参数构造函数以及一个用于设置初始路径的构造函数。在没有转义的情况下,字面逗号无法传递给标记扩展。
转义序列和标记扩展
XAML 处理器中的属性处理使用大括号作为标记扩展序列的指示器。 如有必要,可以通过输入一个转义序列,这个序列包括一个空的大括号对,后接一个大括号字符,从而生成一个原义大括号字符的属性值。 请参阅 {} 转义序列 - 标记扩展。
在 XAML 用法中嵌套标记扩展
支持嵌套多个标记扩展,并且将从最深层开始进行评估每个标记扩展。 例如,请考虑以下用法:
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
在此用法中,首先计算 x:Static
语句并返回一个字符串。 然后,该字符串用作 DynamicResource
的参数。
标记扩展和属性元素语法
当用作填充属性元素值的对象元素时,标记扩展类在视觉上与可在 XAML 中使用的典型类型支持的对象元素没有区别。 典型对象元素和标记扩展之间的实际区别在于,标记扩展要么被计算为一个类型化的值,要么作为一个表达式被延迟处理。 因此,标记扩展属性值的任何可能的类型错误机制都将有所不同,这类似于其他编程模型中对晚绑定属性的对待方式。 将针对在分析 XAML 时设置的目标属性计算普通对象元素的类型匹配。
大多数标记扩展在对象元素语法中使用以填充属性元素时,不会包含内容或任何进一步的属性元素语法。 因此,你将关闭对象元素标记,并且不提供子元素。 每当 XAML 处理器遇到任何对象元素时,将调用该类的构造函数,该构造函数实例化从分析的元素创建的对象。 标记扩展类没有区别:如果希望标记扩展在对象元素语法中可用,则必须提供无参数构造函数。 一些现有的标记扩展至少有一个必需的属性值,必须为实现有效的初始化而指定。 如果是这样,该属性值通常作为对象元素的属性属性提供。 在 XAML 命名空间(x:)语言功能 和 WPF XAML 扩展 引用页中,具有必需属性及其名称的标记扩展将被标记出。 引用页还会注明特定标记扩展是否不允许使用对象元素语法或属性语法。 一个值得注意的情况是 x:Array 标记扩展,它不支持属性语法,因为该数组的内容必须在标记中指定为内容。 数组内容作为常规对象进行处理,因此该属性的默认类型转换器不可行。 此外,x:Array 标记扩展 需要 type
参数。