2.6 标记扩展
标记扩展,就像类型转换器一样,可以用于扩展XAML的表达能力。它们都可以在运行时计算字符串特性的值(除了一些内建的、为提高性能而在编译时计算的标记扩展),并生成一个合适的基于字符串的对象。就像类型转换器一样,WPF有好几个内建的标记扩展,你会发现它们都派生自本书最前面的内封中的MarkupExtension。
但与类型转换器不同的是,标记扩展是通过XAML的显式的、一致的语法调用的,因此,标记扩展是最好的扩展XAML的方法。另外,标记扩展还可以用于克服现存类型转换器的一些限制,对于这些限制本来你是无能为力的。例如,如果你想用一个简单的字符串将控件的背景色设置为漂亮的渐变笔刷(gradient brush),虽然内建的BrushConverter无法完成,但你可以写一个自定义的标记扩展来实现。
只要特性值由花括号({})括起来,XAML编译器或解析器就会把它认作一个标记扩展值而不是一个普通的字符串(或其他一些需要进行类型转换的东西)。下面的按钮使用了3个不同的标记扩展类型,其中分别用到了3个不同的特性:
|
|
|
每个花括号中的第一个识别符是标记扩展类的名称。按照惯例,这样的类都以Extension后缀结尾,但是当你在XAML中使用它时,可以不用该后缀。在这个例子中,NullExtension(我们看到的是x:Null)和StaticExtension(我们看到的是x:Static)是System.Windows.Markup命名空间的类,因此必须使用前缀x来定位它们。Binding(没有Extension后缀)是在System.Windows.Data命名空间下的,因此在默认的XML命名空间下就可以找到它。
如果标记扩展支持,可以使用逗号分隔的参数来指定它的值。
定位参数(例如本例中的SystemParameters.IconHeight)是被作为字符串参数传入扩展类的相应构造函数中的。命名参数(如本例中的Path和RelativeSource)可以用来在已构造好的扩展对象上设置相应名字的属性。这些属性的值可以是标记扩展值自己(使用嵌套的花括号,如RelativeSource的值),也可以是文本值,它们可以通过普通的类型转换过程。如果你熟悉.NET自定义特性(.NET Framework比较流行的扩展机制),你很有可能注意到设计和使用标记扩展与设计和使用自定义特性很相似,是有意这么设计的。
在前面的Button声明中,NullExtension允许设置Background笔刷为null,BrushConvertor和许多其他类型转换器都不支持这种情况。这里仅仅是出于演示的目的,设置Background为null没有什么意义。StaticExtension允许使用静态属性、字段、常量和枚举值,而不使用XAML写的硬编码字面值。在这个例子中,Button的高度是遵循操作系统当前的图标高度设置的,这一设置可以通过System.Windows.SystemParameters类的IconHeight静态字段获得。Binding(在第9章中将深入讲解)可以把Content设置为与它的Height属性相同的值。
摆脱花括号
如果你曾经需要设置一个属性特性值为字面值字符串(以左花括号开始),就必须摆脱它以免把它作为标记扩展。我们可以通过在其之前增加一对空花括号来实现。例如:
若不这样做,你也可以使用属性元素语法而不用任何摆脱,这是因为花括号在上下文中不会有特殊的意义。之前的Button可以用下面的代码重写(因为Content是内容属性,这里使用了隐式属性元素语法):
因为标记扩展是有默认构造函数的类,它们可以与属性元素语法一起使用,下面的Button声明与之前的声明是一样的:

该转换之所以可以执行是因为这些标记扩展都有与形参化的构造函数的实参(使用属性特性语法的定位实参)对应的属性。例如,StaticExtension有一个Member属性与之前传入到形参化构造函数中的实参意思是一样的,RelativeSource有一个对应于构造函数实参的Mode属性。
标记扩展和过程式代码
由标记扩展完成的实际工作对于每个扩展都是不同的。例如,下面的C#代码与使用NullExtension、StaticExtension和Binding的基于XAML表示的按钮是一个意思:
尽管如此,这里的代码与XAML解析器或编译器使用的机制是不同的,解析器和编译器是依靠每个标记扩展在运行时设置合适的值(本质上是通过调用每个类的ProvideValue方法实现的)。与这一机制完全对应的过程式代码通常很复杂,有时还需要只有解析器才有的上下文(例如,如何解析一个XML命名空间前缀供StaticExtension的Member属性使用)。值得庆幸的是,我们没有理由在过程式代码中这样使用标记扩展!





