在编程级别,XAML 标记扩展是实现 IMarkupExtension 或 IMarkupExtension<T> 接口的类。 可以在 Xamarin.Forms GitHub 存储库的 MarkupExtensions 目录中浏览下述标准标记扩展的源代码。
还可以通过从 IMarkupExtension 或 IMarkupExtension<T> 派生来定义自己的自定义 XAML 标记扩展。 如果标记扩展获取特定类型的值,请使用泛型形式。 几个 Xamarin.Forms 标记扩展就是这种情况:
TypeExtension派生自IMarkupExtension<Type>ArrayExtension派生自IMarkupExtension<Array>DynamicResourceExtension派生自IMarkupExtension<DynamicResource>BindingExtension派生自IMarkupExtension<BindingBase>ConstraintExpression派生自IMarkupExtension<Constraint>
两个 IMarkupExtension 接口各自只定义一种方法,名为 ProvideValue:
public interface IMarkupExtension
{
object ProvideValue(IServiceProvider serviceProvider);
}
public interface IMarkupExtension<out T> : IMarkupExtension
{
new T ProvideValue(IServiceProvider serviceProvider);
}
由于 IMarkupExtension<T> 派生自 IMarkupExtension 并包含 ProvideValue 上的 new 关键字,因此它包含这两种 ProvideValue 方法。
通常情况下,XAML 标记扩展会定义那些对返回值有贡献的属性。 (明显的例外是 NullExtension,其中的 ProvideValue 直接返回 null。)ProvideValue 方法有一个类型为 IServiceProvider 的参数,本文稍后将对此进行讨论。
用于指定颜色的标记扩展
以下 XAML 标记扩展允许你使用色调、饱和度和亮度组件构造 Color 值。 它为颜色的四个组件定义四种属性,包括初始化为 1 的 alpha 组件。 该类派生自 IMarkupExtension<Color>,表示 Color 返回值:
public class HslColorExtension : IMarkupExtension<Color>
{
public double H { set; get; }
public double S { set; get; }
public double L { set; get; }
public double A { set; get; } = 1.0;
public Color ProvideValue(IServiceProvider serviceProvider)
{
return Color.FromHsla(H, S, L, A);
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<Color>).ProvideValue(serviceProvider);
}
}
由于 IMarkupExtension<T> 派生自 IMarkupExtension,因此该类必须包含两个 ProvideValue 方法,一个返回 Color,另一个返回 object,但第二个方法可以直接调用第一个方法。
“HSL 颜色演示”页展示了 HslColorExtension 在 XAML 文件中出现以指定 BoxView 颜色的多种方式:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.HslColorDemoPage"
Title="HSL Color Demo">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="BoxView">
<Setter Property="WidthRequest" Value="80" />
<Setter Property="HeightRequest" Value="80" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<BoxView>
<BoxView.Color>
<local:HslColorExtension H="0" S="1" L="0.5" A="1" />
</BoxView.Color>
</BoxView>
<BoxView>
<BoxView.Color>
<local:HslColor H="0.33" S="1" L="0.5" />
</BoxView.Color>
</BoxView>
<BoxView Color="{local:HslColorExtension H=0.67, S=1, L=0.5}" />
<BoxView Color="{local:HslColor H=0, S=0, L=0.5}" />
<BoxView Color="{local:HslColor A=0.5}" />
</StackLayout>
</ContentPage>
请注意,当 HslColorExtension 是 XML 标记时,四个属性被设置为特性,但当它出现在大括号之间时,四个属性用逗号分隔,不带引号。 H、S 和 L 的默认值为 0,A 的默认值为 1,因此如果希望将这些属性设置为默认值,则可以省略这些属性。 最后一个示例演示了亮度为 0 的情况,这通常会导致显示黑色,但 alpha 通道为 0.5,因此它是半透明的,并且在页面的白色背景下显示为灰色:
用于访问位图的标记扩展
ProvideValue 的参数是一个实现 IServiceProvider 接口的对象,该接口在 .NET System 命名空间中定义。 该接口有一个成员,即名为 GetService 且带有 Type 参数的方法。
下面显示的 ImageResourceExtension 类显示了 IServiceProvider 和 GetService 的一种可能用途,即用于获取 IXmlLineInfoProvider 对象,该对象可以提供指示在何处检测到特定错误的行和字符信息。 在这种情况下,如果尚未设置 Source 属性,则会引发异常:
[ContentProperty("Source")]
class ImageResourceExtension : IMarkupExtension<ImageSource>
{
public string Source { set; get; }
public ImageSource ProvideValue(IServiceProvider serviceProvider)
{
if (String.IsNullOrEmpty(Source))
{
IXmlLineInfoProvider lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
IXmlLineInfo lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
throw new XamlParseException("ImageResourceExtension requires Source property to be set", lineInfo);
}
string assemblyName = GetType().GetTypeInfo().Assembly.GetName().Name;
return ImageSource.FromResource(assemblyName + "." + Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<ImageSource>).ProvideValue(serviceProvider);
}
}
当 XAML 文件需要访问作为嵌入资源存储在 .NET Standard 库项目中的图像文件时,ImageResourceExtension 非常有用。 它使用 Source 属性调用静态 ImageSource.FromResource 方法。 此方法需要完全限定的资源名称,其中包含程序集名称、文件夹名称和以句点分隔的文件名。 ImageSource.FromResource 方法的第二个参数提供程序集名称,仅对于 UWP 上的发布版本来说是必需的。 无论如何,必须从包含位图的程序集中调用 ImageSource.FromResource,这意味着此 XAML 资源扩展不能成为外部库的一部分,除非图像也在该库中。 (若要详细了解如何访问存储为嵌入的资源的位图,请参阅嵌入图像一文。)
尽管 ImageResourceExtension 要求设置 Source 属性,但 Source 属性在特性中指示为类的内容属性。 这意味着大括号中的表达式的 Source= 部分可以省略。 在“图像资源演示”页中,Image 元素使用以句点分隔的文件夹名称和文件名来提取两个图像:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.ImageResourceDemoPage"
Title="Image Resource Demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Source="{local:ImageResource Images.SeatedMonkey.jpg}"
Grid.Row="0" />
<Image Source="{local:ImageResource Images.FacePalm.jpg}"
Grid.Row="1" />
</Grid>
</ContentPage>
下面是正在运行的程序:
服务提供商
通过使用 ProvideValue 的 IServiceProvider 参数,XAML 标记扩展可以访问有关使用它们的 XAML 文件的有用信息。 但是,若要成功使用 IServiceProvider 参数,你需要了解特定上下文中可用的服务类型。 了解此功能的最佳方法是研究 GitHub 上 Xamarin.Forms 存储库的 MarkupExtensions 文件夹中现有 XAML 标记扩展的源代码。 请注意,某些类型的服务是 Xamarin.Forms 的内部服务。
在某些 XAML 标记扩展中,此服务可能很有用:
IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
IProvideValueTarget 接口定义了两种属性:TargetObject 和 TargetProperty。 在 ImageResourceExtension 类中获取此信息时,TargetObject 是 Image,TargetProperty 是 Image 的 Source 属性的 BindableProperty 对象。 这是已设置 XAML 标记扩展的属性。
使用参数 typeof(IProvideValueTarget) 的 GetService 调用实际上返回类型为 SimpleValueTargetProvider 的对象,该类型在 Xamarin.Forms.Xaml.Internals 命名空间中定义。 如果将 GetService 的返回值强制转换为该类型,你还可以访问 ParentObjects 属性,该属性是一个数组,其中包含 Image 元素、Grid 父项以及 Grid 的 ImageResourceDemoPage 父项。
结论
XAML 标记扩展通过扩展从各种源设置特性的功能,在 XAML 中发挥着至关重要的作用。 此外,如果现有的 XAML 标记扩展不能完全提供你需要的内容,你也可以编写自己的标记扩展。

