第 3 部 XAML マークアップ拡張
XAML マークアップ拡張は、他のソースから間接的に参照されるオブジェクトまたは値にプロパティを設定できるようにする XAML の重要な機能です。 XAML マークアップ拡張は、オブジェクトを共有したり、アプリケーション全体で使用される定数を参照したりするために特に重要ですが、データ バインディングでその最も優れた有用性を発揮します。
XAML マークアップ拡張
一般に、XAML は、オブジェクトのプロパティに明示的な値 (文字列、数値、列挙メンバー、バックグラウンドで値に変換される文字列など) を設定するために使用します。
ただし、プロパティが別の場所で定義されている値を参照する必要がある場合や、ランタイムにコードによる処理が少し必要になる場合があります。 このような目的で、XAML マークアップ拡張機能を使用できます。
これらの XAML マークアップ拡張は XML の拡張機能ではありません。 XAML は完全に有効な XML です。 これらが "拡張" と呼ばれるのは、IMarkupExtension
を実装するクラスのコードによってサポートされるためです。 独自のカスタム マークアップ拡張を記述できます。
多くの場合、XAML マークアップ拡張は、中かっこ { と } で区切られた属性設定として表示されるため、XAML ファイルですぐに認識できますが、マークアップでマークアップ拡張が従来の要素として表示される場合があります。
共有リソース
一部の XAML ページには、プロパティが同じ値に設定された複数のビューが含まれています。 たとえば、これらの Button
オブジェクトのプロパティ設定の多くは同じです。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<StackLayout>
<Button Text="Do this!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
<Button Text="Do that!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
<Button Text="Do the other thing!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
</StackLayout>
</ContentPage>
これらのプロパティのいずれかを変更する必要がある場合は、変更を 3 回ではなく 1 回だけ行った方がいいでしょう。 これがコードの場合は、定数と静的な読み取り専用オブジェクトを使用すると、このような値の一貫性を保ち、変更しやすくなる可能性があります。
XAML で一般的な解決策の 1 つは、このような値またはオブジェクトをリソース ディクショナリに格納することです。 VisualElement
クラスは、ResourceDictionary
型の Resources
という名前のプロパティを定義します。これは、string
型のキーとobject
型の値があるディクショナリです。 このディクショナリにオブジェクトを配置し、マークアップから参照できます。これはすべて XAML で実行できます。
ページでリソース ディクショナリを使用するには、Resources
プロパティ要素タグのペアを含めます。 これらはページの上部に配置するのが最も便利です。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
</ContentPage.Resources>
...
</ContentPage>
また、ResourceDictionary
タグを明示的に含める必要もあります。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
さまざまな種類のオブジェクトと値をリソース ディクショナリに追加できるようになりました。 これらの型はインスタンス化できなくてはなりません。 たとえば、抽象クラスにすることはできません。 この型には、パラメーターのない公開用コンストラクターが必要です。 各項目には、x:Key
属性で指定されたディクショナリ キーが必要です。 次に例を示します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
これら 2 つの項目は構造体型 LayoutOptions
の値であり、それぞれに一意のキーと 1 つまたは 2 つのプロパティが設定されています。 コードとマークアップでは、LayoutOptions
の静的フィールドを使用する方がはるかに一般的ですが、ここではプロパティを設定する方が便利です。
次に、これらのボタンの HorizontalOptions
および VerticalOptions
プロパティをこれらのリソースに設定する必要があります。これは StaticResource
XAML マークアップ拡張を使用して行われます。
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
StaticResource
マークアップ拡張は常に中かっこで区切られ、ディクショナリ キーが含まれます。
名前 StaticResource
で DynamicResource
と区別されます。これも、Xamarin.Forms でサポートされています。 DynamicResource
は、ランタイム中に変更される可能性のある値に関連付けられているディクショナリ キー用です。一方、StaticResource
は、ページ上の要素が構築されたときに 1 回だけディクショナリから要素にアクセスします。
BorderWidth
プロパティの場合は、ディクショナリに double を格納する必要があります。 XAML は、x:Double
や x:Int32
のような一般的なデータ型のタグを簡単に定義します。
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">
3
</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
3 行に配置する必要はありません。 この回転角度に対するこのディクショナリ エントリは、最大でも 1 行のみを占めます。
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">
3
</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
これらの 2 つのリソースは、LayoutOptions
値と同じ方法で参照できます。
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="Red"
FontSize="24" />
Color
型のリソースの場合、これらの型の属性を直接割り当てるときに使用するものと同じ文字列表現を使用できます。 型コンバーターは、リソースの作成時に呼び出されます。 Color
型のリソースを次に示します。
<Color x:Key="textColor">Red</Color>
多くの場合、プログラムは、FontSize
プロパティを Large
などの NamedSize
列挙型のメンバーに設定します。 FontSizeConverter
クラスはバックグラウンドで動作し、Device.GetNamedSized
メソッドを使用してそれをプラットフォーム依存の値に変換します。 ただし、フォントサイズ リソースを定義する場合は、次に x:Double
型として示すように、数値を使用するほうが理にかなっています。
<x:Double x:Key="fontSize">24</x:Double>
これで、Text
を除くすべてのプロパティがリソース設定で定義されました。
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
また、リソース ディクショナリ内の OnPlatform
を使用して、プラットフォームのさまざまな値を定義することもできます。 OnPlatform
オブジェクトをさまざまなテキストの色のリソース ディクショナリに含める方法を次に示します。
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>
OnPlatform
は、x:Key
属性 (ディクショナリ内のオブジェクトであるため) と、x:TypeArguments
属性 (ジェネリック クラスであるため) の両方を取得することに注意してください。 iOS
、Android
、UWP
の各属性は、オブジェクトの初期化時に Color
値に変換されます。
6 つの共有値にアクセスする 3 つのボタンを含む最終的な完全な XAML ファイルを次に示します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">3</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>
<x:Double x:Key="fontSize">24</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do that!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do the other thing!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
</StackLayout>
</ContentPage>
スクリーンショットでは、一貫性のあるスタイル設定と、プラットフォームに依存するスタイル設定が確認されます。
ページの上部に Resources
のコレクションを定義するのが最も一般的ですが、Resources
プロパティが VisualElement
によって定義されていることに注意してください。また、ページ上の他の要素に Resources
のコレクションを含めることができます。 たとえば、次の例で StackLayout
に 1 つ追加してみます。
<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<Color x:Key="textColor">Blue</Color>
</ResourceDictionary>
</StackLayout.Resources>
...
</StackLayout>
ボタンのテキストの色が青になったことがわかります。 基本的に、XAML パーサーは、StaticResource
マークアップ拡張を検出するたびに、ビジュアル ツリーを検索し、そのキーを含む最初に検出された ResourceDictionary
を使用します。
リソース ディクショナリに格納されるオブジェクトの最も一般的な種類の 1 つは Xamarin.FormsStyle
で、これはプロパティ設定のコレクションを定義します。 スタイルについては、スタイルに関する記事で説明します。
XAML を初めて使用する開発者は、Label
や Button
などのビジュアル要素を ResourceDictionary
に配置できるだろうかと思う場合があります。 確かに可能ではありますが、あまり意味はありません。 ResourceDictionary
の目的は、オブジェクトを共有することです。 ビジュアル要素は共有できません。 同じインスタンスを 1 つのページに 2 回表示することはできません。
x:Static マークアップ拡張
名前は似ていますが、x:Static
と StaticResource
は大きく異なります。 StaticResource
はリソース ディクショナリからオブジェクトを返しますが、x:Static
は次のいずれかにアクセスします。
- パブリック静的フィールド
- パブリック静的プロパティ
- パブリック定数フィールド
- 列挙メンバー。
StaticResource
マークアップ拡張は、リソース ディクショナリを定義する XAML 実装でサポートされていますが、x:Static
は x
プレフィックスから明らかなように XAML の組み込み部分です。
次に、x:Static
が静的フィールドと列挙型メンバーを明示的に参照する方法を示す例をいくつか示します。
<Label Text="Hello, XAML!"
VerticalOptions="{x:Static LayoutOptions.Start}"
HorizontalTextAlignment="{x:Static TextAlignment.Center}"
TextColor="{x:Static Color.Aqua}" />
ここまでは、関心する要素はあまりありません。 ただし、x:Static
マークアップ拡張では、独自のコードから静的フィールドまたはプロパティを参照することもできます。 たとえば、アプリケーション全体で複数のページで使用する可能性のあるいくつかの静的フィールドが含まれる AppConstants
クラスを次に示します。
using System;
using Xamarin.Forms;
namespace XamlSamples
{
static class AppConstants
{
public static readonly Thickness PagePadding;
public static readonly Font TitleFont;
public static readonly Color BackgroundColor = Color.Aqua;
public static readonly Color ForegroundColor = Color.Brown;
static AppConstants()
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
PagePadding = new Thickness(5, 20, 5, 0);
TitleFont = Font.SystemFontOfSize(35, FontAttributes.Bold);
break;
case Device.Android:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(40, FontAttributes.Bold);
break;
case Device.UWP:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(50, FontAttributes.Bold);
break;
}
}
}
}
XAML ファイルでこのクラスの静的フィールドを参照するには、XAML ファイル内で、このファイルが配置されている場所をなんらかの方法で指定する必要があります。 これを行うには、XML 名前空間を宣言します。
標準の Xamarin.Forms XAML テンプレートの一部として作成された XAML ファイルには、2 つの XML 名前空間の宣言が含まれていることを思い出してください。1 つは Xamarin.Forms クラスにアクセスするための宣言で、もう 1 つは XAML に組み込まれているタグと属性を参照するための宣言です。
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
他のクラスにアクセスするには、XML 名前空間を追加で宣言する必要があります。 追加の XML 名前空間宣言ごとに、新しいプレフィックスが定義されます。 AppConstants
などの共有アプリケーション .NET Standard ライブラリにローカルなクラスにアクセスするために、XAML プログラマは多くの場合、プレフィックス local
を使用します。 名前空間宣言は、CLR (共通言語ランタイム) 名前空間名 (.NET 名前空間名とも呼ばれます) を示す必要があります。これは、C# namespace
定義または using
ディレクティブに表示される名前です。
xmlns:local="clr-namespace:XamlSamples"
.NET Standard ライブラリが参照する任意のアセンブリで、.NET 名前空間の XML 名前空間宣言を定義することもできます。 たとえば、次に示すのは標準の .NET System
名前空間の sys
プレフィックスです。これは、netstandard アセンブリにあります。 これは別のアセンブリであるため、アセンブリ名 (この場合 netstandard) も指定する必要があります。
xmlns:sys="clr-namespace:System;assembly=netstandard"
キーワード clr-namespace
の後には、コロン、.NET 名前空間名、セミコロン、キーワード assembly
、等号、アセンブリ名が続きます。
clr-namespace
の後にコロンが続き、assembly
の後には等号が続きます。 構文は意図的にこのように定義されています。ほとんどの XML 名前空間宣言は、http
などの URI スキーム名を開始する URI を参照します。これには、常にコロンが続きます。 この文字列の clr-namespace
の部分は、その規則を模倣することを目的としています。
これらの名前空間宣言はどちらも、StaticConstantsPage サンプルに含まれています。 BoxView
ディメンションは Math.PI
と Math.E
に設定されていますが、係数 100 でスケーリングされます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.StaticConstantsPage"
Title="Static Constants Page"
Padding="{x:Static local:AppConstants.PagePadding}">
<StackLayout>
<Label Text="Hello, XAML!"
TextColor="{x:Static local:AppConstants.BackgroundColor}"
BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
Font="{x:Static local:AppConstants.TitleFont}"
HorizontalOptions="Center" />
<BoxView WidthRequest="{x:Static sys:Math.PI}"
HeightRequest="{x:Static sys:Math.E}"
Color="{x:Static local:AppConstants.ForegroundColor}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="100" />
</StackLayout>
</ContentPage>
画面を基準にした結果の BoxView
のサイズはプラットフォームによって異なります。
その他の標準マークアップ拡張
いくつかのマークアップ拡張は XAML に組み込まれており、Xamarin.Forms XAML ファイルでサポートされています。 これらの一部はあまり使用されませんが、必要な場合は不可欠です。
- プロパティに既定で
null
以外の値があり、それをnull
に設定する場合は、{x:Null}
マークアップ拡張に設定します。 - プロパティが
Type
型の場合は、マークアップ拡張{x:Type someClass}
を使用してType
オブジェクトに割り当てることができます。 x:Array
マークアップ拡張を使用して、XAML で配列を定義できます。 このマークアップ拡張には、配列内の要素の型を示す、Type
という名前の必須の属性があります。Binding
マークアップ拡張については、「パート 4. データ バインディングの基礎」で説明します。RelativeSource
マークアップ拡張については、相対バインディングに関するページで説明します。
ConstraintExpression マークアップ拡張
マークアップ拡張はプロパティを持つことができますが、XML 属性のように設定されません。 マークアップ拡張では、プロパティ設定はコンマで区切られ、中かっこ内に引用符は表示されません。
これは、RelativeLayout
クラスで使用される ConstraintExpression
という名前の Xamarin.Forms マークアップ拡張を使用して示すことができます。 子ビューの場所またはサイズは、定数として指定することも、親ビューやその他の名前付きビューを基準にして指定することもできます。 ConstraintExpression
の構文を使用すると、別のビューのプロパティを Factor
倍にし、Constant
を追加して、ビューの位置またはサイズを設定できます。 それ以上に複雑なものにはコードが必要です。
次に例を示します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.RelativeLayoutPage"
Title="RelativeLayout Page">
<RelativeLayout>
<!-- Upper left -->
<BoxView Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Upper right -->
<BoxView Color="Green"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Lower left -->
<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />
<!-- Lower right -->
<BoxView Color="Yellow"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />
<!-- Centered and 1/3 width and height of parent -->
<BoxView x:Name="oneThird"
Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}" />
<!-- 1/3 width and height of previous -->
<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=X}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Y}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Height,
Factor=0.33}" />
</RelativeLayout>
</ContentPage>
おそらく、このサンプルから学べる最も重要なレッスンは、マークアップ拡張の構文です。マークアップ拡張の中かっこ内に引用符を含める必要はありません。 XAML ファイルにマークアップ拡張を入力するときに、プロパティの値を引用符で囲みたくなるのは自然なことです。 その誘惑に抵抗しましょう。
実行されているプログラムを次に示します。
まとめ
ここに示す XAML マークアップ拡張は、XAML ファイルに重要なサポートを提供します。 しかし、おそらく最も価値のある XAML マークアップ拡張は Binding
です。これについては、このシリーズの次のパートである「パート 4. データ バインディングの基礎」で説明します。