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