第 2 部 基本的な XAML 構文

Download Sampleサンプルのダウンロード

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 のオブジェクトです。
  • TextVerticalOptionsFontAttributesFontSize は、プロパティ属性です。 これらは 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 には RowDefinitionsColumnDefinitions という名前の 2 つのプロパティがあります。 これら 2 つのプロパティの型は RowDefinitionCollectionColumnDefinitionCollection であり、RowDefinitionColumnDefinition の各オブジェクトのコレクションです。 これらのコレクションを設定するには、プロパティ要素構文を使用する必要があります。

こちらが GridDemoPage クラスの XAML ファイルの先頭です。RowDefinitionsColumnDefinitions の各コレクションのプロパティ要素タグを示します。

<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>

サイズが自動で調整されるセル、幅と高さをピクセル単位で指定するセル、星の設定を定義する、省略形の構文に注目してください。

添付プロパティ

行と列を定義するために、GridRowDefinitionsColumnDefinitions の各コレクションのプロパティ要素が必要であることを確認しました。 ただし、プログラマが 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.RowGrid.Column の設定は不要ですが、一般的にわかりやすくするために含まれています。

次のようになります。

Grid Layout

構文から判断すると、これらの Grid.RowGrid.ColumnGrid.RowSpanGrid.ColumnSpan の各属性は Grid の静的なフィールドまたはプロパティのように見えますが、大変興味深いことに、Grid によって RowColumnRowSpanColumnSpan という名前のものは一切定義されません。

代わりに、Grid によって、RowPropertyColumnPropertyRowSpanPropertyColumnSpanProperty という名前の 4 つのバインド可能プロパティが定義されます。 これらは特殊なタイプのバインド可能プロパティで、"添付プロパティ" と呼ばれます。 これらは Grid クラスによって定義されますが、Grid の子に設定されます。

コードでこれらの添付プロパティを使用する場合、Grid クラスによって SetRowGetColumn などのような静的メソッドが提供されます。 ただし、XAML では、これらの添付プロパティは、シンプルなプロパティ名を使用する Grid の子の属性として設定されます。

添付プロパティは、常に XAML ファイルで、ピリオドで区切られたクラス名とプロパティ名の両方が含まれる属性として認識できます。 これらは 1 つのクラス (この場合は Grid) によって定義されますが、他のオブジェクト (この場合は Grid の子) に添付されるため、添付プロパティと呼ばれます。 Grid は、レイアウト中にこれらの添付プロパティの値を調べて、各子をどこに配置するかを知ることができます。

AbsoluteLayout クラスにより、LayoutBoundsLayoutFlags という名前の 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>

次のとおりです。

Absolute Layout

このようなものについては、XAML の使用が妥当であるかについて疑問に感じるかもしれません。 確かに、LayoutBounds の四角形の繰り返しと規則性は、コードを使用したほうが実現が簡単であることを示唆しているかもしれません。

この懸念は確かにもっともであり、ユーザー インターフェイスを定義する際のコードとマークアップの使用のバランスを取ることについてまったく異存はありません。 XAML でビジュアルの一部を定義し、分離コード ファイルのコンストラクターを使用して、ループでより適切に生成される可能性のあるビジュアルをさらに追加するのは簡単です。

コンテンツ プロパティ

前の例では、StackLayoutGridAbsoluteLayout の各オブジェクトは ContentPageContent プロパティに設定され、それらのレイアウトの子は実際には 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 プロパティに割り当てられるものと見なされます。

StackLayoutGridAbsoluteLayoutRelativeLayout はすべて 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>

OnPlatform プロパティの型は 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 マークアップ拡張」で取り上げています。