第 2 部 基本的な XAML 構文
XAML は、主にオブジェクトのインスタンス化と初期化を目的として設計されています。 ただし、多くの場合、プロパティは XML 文字列として簡単に表すことができない複合オブジェクトに設定する必要があり、1 つのクラスで定義されたプロパティを子クラスに設定する必要がある場合があります。 これら 2 つのニーズには、プロパティ要素と添付プロパティの基本的な XAML 構文機能が必要です。
プロパティ要素
XAML では、クラスのプロパティは通常、XML 属性として設定されます。
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large"
TextColor="Aqua" />
ただし、XAML でプロパティを設定する別の方法があります。 TextColor
でこの代替方法を試すには、まず TextColor
の既存の設定を削除します。
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large" />
開始タグと終了タグに分けることで、要素が空の Label
タグを開きます。
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
</Label>
これらのタグ内に、ピリオドで区切られたクラス名とプロパティ名で構成される開始タグと終了タグを追加します。
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
<Label.TextColor>
</Label.TextColor>
</Label>
次のように、これらの新しいタグのコンテンツとしてプロパティ値を設定します。
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>
TextColor
プロパティを指定するこれら 2 つの方法は機能的には同等ですが、同じプロパティに対してこの 2 つの方法を両方使用することはしないでください。実質的にプロパティを 2 回設定することになり、あいまいになる可能性があるためです。
この新しい構文では、次のいくつかの便利な用語を導入できます。
Label
は、オブジェクト要素です。 XML 要素として表現される Xamarin.Forms のオブジェクトです。Text
、VerticalOptions
、FontAttributes
、FontSize
は、プロパティ属性です。 これらは XML 属性として表現される Xamarin.Forms のプロパティです。- その最後のスニペットでは、
TextColor
が "プロパティ要素" になっています。 これは Xamarin.Forms のプロパティですが、XML 要素になりました。
プロパティ要素の定義は、一見 XML 構文に違反していると思われるかもしれませんが、そうではありません。 XML では、ピリオドは特別な意味を持っていません。 XML デコーダーにとって、Label.TextColor
は普通の子要素にすぎません。
ただし、XAML では、この構文は非常に特殊です。 プロパティ要素のルールの 1 つは、Label.TextColor
タグ内に他に何も表示できないということです。 プロパティの値は、プロパティ要素の開始タグと終了タグの間のコンテンツとして定義されます。
プロパティ要素構文は、1 つ以上のプロパティで使用できます。
<Label Text="Hello, XAML!"
VerticalOptions="Center">
<Label.FontAttributes>
Bold
</Label.FontAttributes>
<Label.FontSize>
Large
</Label.FontSize>
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>
プロパティ要素構文をすべてのプロパティに使用することもできます。
<Label>
<Label.Text>
Hello, XAML!
</Label.Text>
<Label.FontAttributes>
Bold
</Label.FontAttributes>
<Label.FontSize>
Large
</Label.FontSize>
<Label.TextColor>
Aqua
</Label.TextColor>
<Label.VerticalOptions>
Center
</Label.VerticalOptions>
</Label>
一見すると、プロパティ要素構文は、かなりシンプルな構文を不必要に長くした構文のように見えるかもしれません。これらはまさしくそのような例です。
しかし、単純な文字列として表現するにはプロパティの値が複雑すぎる場合、プロパティ要素構文は不可欠になります。 プロパティ要素のタグ内では、別のオブジェクトをインスタンス化し、そのプロパティを設定できます。 たとえば、プロパティ設定を使用して、VerticalOptions
のようなプロパティを LayoutOptions
値に明示的に設定できます。
<Label>
...
<Label.VerticalOptions>
<LayoutOptions Alignment="Center" />
</Label.VerticalOptions>
</Label>
別の例を挙げましょう。Grid
には RowDefinitions
と ColumnDefinitions
という名前の 2 つのプロパティがあります。 これら 2 つのプロパティの型は RowDefinitionCollection
と ColumnDefinitionCollection
であり、RowDefinition
と ColumnDefinition
の各オブジェクトのコレクションです。 これらのコレクションを設定するには、プロパティ要素構文を使用する必要があります。
こちらが GridDemoPage
クラスの XAML ファイルの先頭です。RowDefinitions
と ColumnDefinitions
の各コレクションのプロパティ要素タグを示します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
サイズが自動で調整されるセル、幅と高さをピクセル単位で指定するセル、星の設定を定義する、省略形の構文に注目してください。
添付プロパティ
行と列を定義するために、Grid
に RowDefinitions
と ColumnDefinitions
の各コレクションのプロパティ要素が必要であることを確認しました。 ただし、プログラマが Grid
の各子が存在する行と列を示す何らかの方法も必要です。
Grid
の各子のタグ内に、次の属性を使用してその子の行と列を指定します。
Grid.Row
Grid.Column
これらの属性の既定値は 0 です。 また、これらの属性を使用して、子が複数の行や列にまたがっているかどうかを示すこともできます。
Grid.RowSpan
Grid.ColumnSpan
これら 2 つの属性の既定値は 1 です。
完全な GridDemoPage.xaml ファイルを次に示します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Text="Autosized cell"
Grid.Row="0" Grid.Column="0"
TextColor="White"
BackgroundColor="Blue" />
<BoxView Color="Silver"
HeightRequest="0"
Grid.Row="0" Grid.Column="1" />
<BoxView Color="Teal"
Grid.Row="1" Grid.Column="0" />
<Label Text="Leftover space"
Grid.Row="1" Grid.Column="1"
TextColor="Purple"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Span two rows (or more if you want)"
Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"
TextColor="Yellow"
BackgroundColor="Blue"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Span two columns"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
TextColor="Blue"
BackgroundColor="Yellow"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Fixed 100x100"
Grid.Row="2" Grid.Column="2"
TextColor="Aqua"
BackgroundColor="Red"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
0 になっている Grid.Row
と Grid.Column
の設定は不要ですが、一般的にわかりやすくするために含まれています。
次のようになります。
構文から判断すると、これらの Grid.Row
、Grid.Column
、Grid.RowSpan
、Grid.ColumnSpan
の各属性は Grid
の静的なフィールドまたはプロパティのように見えますが、大変興味深いことに、Grid
によって Row
、Column
、RowSpan
、ColumnSpan
という名前のものは一切定義されません。
代わりに、Grid
によって、RowProperty
、ColumnProperty
、RowSpanProperty
、ColumnSpanProperty
という名前の 4 つのバインド可能プロパティが定義されます。 これらは特殊なタイプのバインド可能プロパティで、"添付プロパティ" と呼ばれます。 これらは Grid
クラスによって定義されますが、Grid
の子に設定されます。
コードでこれらの添付プロパティを使用する場合、Grid
クラスによって SetRow
、GetColumn
などのような静的メソッドが提供されます。 ただし、XAML では、これらの添付プロパティは、シンプルなプロパティ名を使用する Grid
の子の属性として設定されます。
添付プロパティは、常に XAML ファイルで、ピリオドで区切られたクラス名とプロパティ名の両方が含まれる属性として認識できます。 これらは 1 つのクラス (この場合は Grid
) によって定義されますが、他のオブジェクト (この場合は Grid
の子) に添付されるため、添付プロパティと呼ばれます。 Grid
は、レイアウト中にこれらの添付プロパティの値を調べて、各子をどこに配置するかを知ることができます。
AbsoluteLayout
クラスにより、LayoutBounds
と LayoutFlags
という名前の 2 つの添付プロパティが定義されます。 こちらが AbsoluteLayout
の比例位置とサイズ設定機能を使用して実現したチェッカーボード パターンです。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.AbsoluteDemoPage"
Title="Absolute Demo Page">
<AbsoluteLayout BackgroundColor="#FF8080">
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.33, 0, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="1, 0, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0, 0.33, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.67, 0.33, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.33, 0.67, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="1, 0.67, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0, 1, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
<BoxView Color="#8080FF"
AbsoluteLayout.LayoutBounds="0.67, 1, 0.25, 0.25"
AbsoluteLayout.LayoutFlags="All" />
</AbsoluteLayout>
</ContentPage>
次のとおりです。
このようなものについては、XAML の使用が妥当であるかについて疑問に感じるかもしれません。 確かに、LayoutBounds
の四角形の繰り返しと規則性は、コードを使用したほうが実現が簡単であることを示唆しているかもしれません。
この懸念は確かにもっともであり、ユーザー インターフェイスを定義する際のコードとマークアップの使用のバランスを取ることについてまったく異存はありません。 XAML でビジュアルの一部を定義し、分離コード ファイルのコンストラクターを使用して、ループでより適切に生成される可能性のあるビジュアルをさらに追加するのは簡単です。
コンテンツ プロパティ
前の例では、StackLayout
、Grid
、AbsoluteLayout
の各オブジェクトは ContentPage
の Content
プロパティに設定され、それらのレイアウトの子は実際には Children
コレクション内の項目です。 ただし、これら Content
プロパティと Children
プロパティは XAML ファイル内のどこにもありません。
次の XamlPlusCode のサンプルのように、確かに Content
プロパティと Children
プロパティをプロパティ要素として含めることはできます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
Title="XAML + Code Page">
<ContentPage.Content>
<StackLayout>
<StackLayout.Children>
<Slider VerticalOptions="CenterAndExpand"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="valueLabel"
Text="A simple Label"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Click Me!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Clicked="OnButtonClicked" />
</StackLayout.Children>
</StackLayout>
</ContentPage.Content>
</ContentPage>
本当の質問は、これらのプロパティ要素がなぜ XAML で "不要" であるのかということです。
XAML で使用するために Xamarin.Forms で定義された要素には、クラスの ContentProperty
属性でフラグが設定されたプロパティを 1 つ指定できます。 Xamarin.Forms のオンライン ドキュメントで ContentPage
クラスを検索すると、次の属性が表示されます。
[Xamarin.Forms.ContentProperty("Content")]
public class ContentPage : TemplatedPage
これはつまり、Content
プロパティ要素タグは不要であるということです。 ContentPage
の開始タグと終了タグの間に表示されるすべての XML コンテンツは、Content
プロパティに割り当てられるものと見なされます。
StackLayout
、Grid
、AbsoluteLayout
、RelativeLayout
はすべて Layout<View>
から派生し、Xamarin.Forms のドキュメントで Layout<T>
を参照すると、別の ContentProperty
属性が表示されます。
[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout ...
これにより、明示的な Children
プロパティ要素タグを使用せずに、レイアウトのコンテンツを Children
コレクションに自動的に追加できます。
その他のクラスにも、ContentProperty
属性定義があります。 たとえば、Label
のコンテンツ プロパティは、Text
です。 その他については、API のドキュメントを確認してください。
OnPlatform でのプラットフォームの違い
シングル ページ アプリケーションでは、iOS ステータス バーが上書きされないように、ページに Padding
プロパティを設定するのが一般的です。 コードでは、この目的のために Device.RuntimePlatform
プロパティを使用します。
if (Device.RuntimePlatform == Device.iOS)
{
Padding = new Thickness(0, 20, 0, 0);
}
また、XAML では、OnPlatform
クラスと On
クラスを使用して、同じようなことができます。 最初に、ページの上部付近にある Padding
プロパティのプロパティ要素を含めます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
</ContentPage.Padding>
...
</ContentPage>
これらのタグ内に、OnPlatform
タグを含めます。 OnPlatform
はジェネリック クラスです。 ジェネリック型引数を指定する必要があります。この場合は Thickness
で、Padding
プロパティの型を示します。 幸いなことに、ジェネリック型引数の定義に特化した x:TypeArguments
という XAML の属性があります。 これは設定するプロパティの型と一致する必要があります。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
OnPlatform
には Platforms
という名前のプロパティがあり、これは On
オブジェクトの IList
です。 そのプロパティにプロパティ要素タグを使用します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
</OnPlatform.Platforms>
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
次に、On
要素を追加します。 それぞれ Platform
プロパティと Value
プロパティを Thickness
プロパティのマークアップに設定します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android" Value="0, 0, 0, 0" />
<On Platform="UWP" Value="0, 0, 0, 0" />
</OnPlatform.Platforms>
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
このマークアップは簡略化できます。 OnPlatform
のコンテンツ プロパティは Platforms
であるため、これらのプロパティ要素タグは削除できます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android" Value="0, 0, 0, 0" />
<On Platform="UWP" Value="0, 0, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
On
の Platform
プロパティの型は IList<string>
であるため、値が同じである場合は複数のプラットフォームを含めることができます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android, UWP" Value="0, 0, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
Android と UWP は既定値 Padding
に設定されているため、そのタグを削除できます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
これは、XAML でプラットフォーム依存の Padding
プロパティを設定する標準的な方法です。 Value
の設定を 1 つの文字列で表すことができない場合は、そのプロパティ要素を定義できます。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS">
<On.Value>
0, 20, 0, 0
</On.Value>
</On>
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
Note
また、プラットフォームごとに UI の外観をカスタマイズするために、OnPlatform
マークアップ拡張を XAML で使用できます。 これは、OnPlatform
および On
クラスと同じ機能を提供しますが、表現がより簡潔になっています。 詳細については、「OnPlatform マークアップ拡張」 を参照してください。
まとめ
基本的な XAML 構文の多くは、プロパティ要素と添付プロパティを使用して確立されています。 ただし、リソース ディクショナリなど、間接的な方法でオブジェクトにプロパティを設定する必要がある場合があります。 このアプローチについては、次の「第 3 部:XAML マークアップ拡張」で取り上げています。