在编程级别,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 标记扩展不能完全提供你需要的内容,你也可以编写自己的标记扩展。

