创建 XAML 标记扩展

在编程级别,XAML 标记扩展是实现 IMarkupExtensionIMarkupExtension<T> 接口的类。 可以在 Xamarin.Forms GitHub 存储库的 MarkupExtensions 目录中浏览下述标准标记扩展的源代码。

还可以通过从 IMarkupExtensionIMarkupExtension<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 标记时,四个属性被设置为特性,但当它出现在大括号之间时,四个属性用逗号分隔,不带引号。 HSL 的默认值为 0,A 的默认值为 1,因此如果希望将这些属性设置为默认值,则可以省略这些属性。 最后一个示例演示了亮度为 0 的情况,这通常会导致显示黑色,但 alpha 通道为 0.5,因此它是半透明的,并且在页面的白色背景下显示为灰色:

HSL 颜色演示

用于访问位图的标记扩展

ProvideValue 的参数是一个实现 IServiceProvider 接口的对象,该接口在 .NET System 命名空间中定义。 该接口有一个成员,即名为 GetService 且带有 Type 参数的方法。

下面显示的 ImageResourceExtension 类显示了 IServiceProviderGetService 的一种可能用途,即用于获取 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>

下面是正在运行的程序:

图像资源演示

服务提供商

通过使用 ProvideValueIServiceProvider 参数,XAML 标记扩展可以访问有关使用它们的 XAML 文件的有用信息。 但是,若要成功使用 IServiceProvider 参数,你需要了解特定上下文中可用的服务类型。 了解此功能的最佳方法是研究 GitHub 上 Xamarin.Forms 存储库的 MarkupExtensions 文件夹中现有 XAML 标记扩展的源代码。 请注意,某些类型的服务是 Xamarin.Forms 的内部服务。

在某些 XAML 标记扩展中,此服务可能很有用:

 IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

IProvideValueTarget 接口定义了两种属性:TargetObjectTargetProperty。 在 ImageResourceExtension 类中获取此信息时,TargetObjectImageTargetPropertyImageSource 属性的 BindableProperty 对象。 这是已设置 XAML 标记扩展的属性。

使用参数 typeof(IProvideValueTarget)GetService 调用实际上返回类型为 SimpleValueTargetProvider 的对象,该类型在 Xamarin.Forms.Xaml.Internals 命名空间中定义。 如果将 GetService 的返回值强制转换为该类型,你还可以访问 ParentObjects 属性,该属性是一个数组,其中包含 Image 元素、Grid 父项以及 GridImageResourceDemoPage 父项。

结论

XAML 标记扩展通过扩展从各种源设置特性的功能,在 XAML 中发挥着至关重要的作用。 此外,如果现有的 XAML 标记扩展不能完全提供你需要的内容,你也可以编写自己的标记扩展。