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> から派生します

2 つの IMarkupExtension インターフェイスは、ProvideValue という名前のそれぞれ 1 つのメソッドのみを定義します。

public interface IMarkupExtension
{
    object ProvideValue(IServiceProvider serviceProvider);
}

public interface IMarkupExtension<out T> : IMarkupExtension
{
    new T ProvideValue(IServiceProvider serviceProvider);
}

IMarkupExtension<T>IMarkupExtension から派生し、ProvideValuenew キーワードが含まれているので、両方の ProvideValue メソッドが含まれます。

多くの場合、XAML マークアップ拡張は、戻り値に寄与するプロパティを定義します。 (明らかな例外は NullExtension です。この場合、ProvideValue によって単に null が返されます。)ProvideValue メソッドには、この記事の後半で説明する IServiceProvider 型の 1 つの引数があります。

色を指定するためのマークアップ拡張

次の XAML マークアップ拡張を使用すると、色相、彩度、および明度の各コンポーネントを使用して Color 値を構築できます。 これは、色の 4 つのコンポーネントの 4 つのプロパティを定義します。1 に初期化されるアルファ コンポーネントも含まれます。 このクラスは、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 から派生しているため、クラスには 2 つの ProvideValue メソッドが含まれている必要があります。1 つは Color を返し、もう 1 つは object を返しますが、2 番目のメソッドは単に最初のメソッドを呼び出すだけで済みます。

[HSL Color Demo] (HSL カラー デモ) ページには、BoxView の色を指定するために HslColorExtension を XAML ファイルに表示するさまざまな方法が示されます。

<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 タグの場合、4 つのプロパティは属性として設定されますが、中かっこの間に表示される場合、4 つのプロパティは引用符なしでコンマで区切られます。 H の既定値は SL は 0、Aは 1 なので、既定値に設定する場合は、これらのプロパティを省略できます。 最後の例では、光度が 0 で、通常は黒になりますが、アルファ チャネルが 0.5 なので半分透明になり、ページの白い背景に対して灰色で表示されています。

HSL カラー デモ

ビットマップにアクセスするためのマークアップ拡張

ProvideValue の引数は、.NET System 名前空間で定義されている IServiceProvider インターフェイスを実装するオブジェクトです。 このインターフェイスには、1 つのメンバー、つまり、Type 引数を持つ GetService という名前のメソッドがあります。

次に示す ImageResourceExtension クラスは、特定のエラーが検出された場所を示す行と文字の情報を提供できる IXmlLineInfoProvider オブジェクトを取得するための、IServiceProviderGetService の 1 つの可能な使用法を示しています。 この場合、次に示すように、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);
    }
}

ImageResourceExtension は、.NET Standard ライブラリ プロジェクトに埋め込みリソースとして保存されている画像ファイルに XAML ファイルがアクセスする必要がある場合に役立ちます。 これは、Source プロパティを使用して静的 ImageSource.FromResource メソッドを呼び出します。 このメソッドには、ピリオドで区切られたアセンブリ名、フォルダー名、ファイル名で構成される完全修飾リソース名が必要です。 ImageSource.FromResource メソッドの 2 番目の引数はアセンブリ名を提供し、UWP 上のリリース ビルドにのみ必要になります。 ただし、それとは関係なく、ImageSource.FromResource は、ビットマップを含むアセンブリから呼び出す必要があります。つまり、画像も外部ライブラリに存在しない限り、この XAML リソース拡張をそのライブラリに含めることはできません。 (埋め込みリソースとして保存されているビットマップへのアクセスに関する詳細については、「埋め込み画像」の記事を参照してください。)

ImageResourceExtension では Source プロパティを設定する必要がありますが、Source プロパティは属性でクラスのコンテンツ プロパティとして示されます。 このことは、中かっこで囲まれた式の Source= 部分は省略できることを意味します。 [Image Resource Demo] (画像リソース デモ) ページでは、Image 要素は、ピリオドで区切られたフォルダー名とファイル名を使用して 2 つの画像をフェッチします。

<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 インターフェイスは 2 つのプロパティ、TargetObjectTargetProperty を定義します。 ImageResourceExtension クラスでこの情報を取得する場合、TargetObjectImage であり、TargetPropertyImageSource プロパティの BindablePropertyオブジェクトです。 これは、XAML マークアップ拡張機能が設定されているプロパティです。

typeof(IProvideValueTarget) の引数を使用した GetService 呼び出しは実際には、Xamarin.Forms.Xaml.Internals 名前空間で定義されている SimpleValueTargetProvider 型のオブジェクトを返します。 GetService の戻り値をその型にキャストする場合、ParentObjects プロパティにもアクセスできます。このプロパティは、Image 要素、Grid 親、GridImageResourceDemoPage 親を含む配列です。

まとめ

XAML マークアップ拡張は、さまざまなソースからの属性を設定する機能を拡張することで、XAML で重要な役割を果たします。 さらに、既存の XAML マークアップ拡張によって必要なものが正確に提供されない場合は、独自のものを記述することもできます。