XAML について

Uno Platform で画面をデザインするときに使用する XAML について説明します。
XAML は eXtensible Application Markup Languge の略です。名前からわかるように画面のデザインが専門の言語ではありません。

XAML はルールは単純で XML の名前空間が C# での名前空間に対応して、タグ名がクラス名に対応します。そして属性がプロパティに対応します。XAML をプログラムで読み込むと、XAML で定義されたクラスのインスタンスが生成されます。

XML 名前空間には URL と `using:Xxxx.Xxxx` の 2 通りの指定方法があります。
URL は、読み込んでいるライブラリー内に URL に対応する名前空間が定義されています。
UWP では、デフォルトで使うクラスの名前空間を表す `http://schemas.microsoft.com/winfx/2006/xaml/presentation` という URL や、XAML 内で使用する各種便利機能の入った `http://schemas.microsoft.com/winfx/2006/xaml` という URL (通常 x という名前空間が指定されます）やデザイナー向けの支援機能を含んだ `http://schemas.microsoft.com/expression/blend/2008` というものがあります。

using を使って名前空間を定義するには `xmlns:examples="using:Sample.Examples"` という形で指定します。これで、`examples:Foo` という名前のタグは `Sample.Examples.Foo` クラスに対応するようになります。

具体例を見ていきます。例えば以下のような C# のクラスが定義されているとします。

```csharp:Person.cs
using System;

namespace Sample.Examples
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
```

このクラスに Name に Kazuki Ota を設定して、年齢に 38 を設定した Person クラスのインスタンスを作るような XAML は以下のようになります。

```xml
<Person xmlns="using:Sample.Examples"
    Name="Kazuki Ota"
    Age="38" />
```

ここまでの内容を踏まえて XAML でのページの定義を見てみましょう。

```xml:MainPage.xaml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    ... 省略 ...
</Page>
```

これは、`new Page()` をしているのと、ほぼ同じです。（厳密には `x:Class` 属性がついてるので追加の処理がされています）

ここから先では、XAML の構文をいくつか紹介したいと思います。

## プロパティ要素構文

XML タグの属性がプロパティへの値の設定だと上で説明しました。文字列や数字のような単純な値の場合はこれで問題ありませんが、プロパティにオブジェクトを設定したいときに困ります。
これを解決するためにプロパティ要素構文があります。プロパティ要素構文は `<クラス名.プロパティ名>プロパティの値</クラス名.プロパティ名>` という形でタグでプロパティを表すことが出来ます。

例えば、ページクラスには Conetnt プロパティがあります。この Content プロパティに設定したものがページに表示されます。この Content プロパティにプロパティ要素構文を使って Hello world という文字を指定した TextBlock を設定した XAML は以下のようになります。

```xml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Content>
        <TextBlock Text="Hello world" />
    </Page.Content>
</Page>
```

プロパティ要素構文を使うとネストしたオブジェクトのインスタンスを定義できるようになります。

## コレクション構文

プロパティがコレクション型の場合は、以下のようにコレクション型のプロパティの直下にタグを複数置くことでコレクションに追加されます。

```xml
<クラス名.コレクション型のプロパティ>
  <要素 />
  <要素 />
  <要素 />
</クラス名.コレクション型のプロパティ>
```

実際の例として StackPanel を紹介したいと思います。StackPanel は Children プロパティに設定されたコントロールを縦や横に並べて表示するコントロールです。この Children プロパティはコレクションなのでコレクション構文が使えます。
以下のように `StackPanel.Children` タグの下に複数の要素を書くことが出来ます。

```xml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Content>
        <StackPanel>
            <StackPanel.Children>
                <TextBlock Text="Hello world" />
                <TextBlock Text="こんにちは世界" />
                <TextBlock Text="コレクション構文のサンプル" />
            </StackPanel.Children>
        </StackPanel>
    </Page.Content>
</Page>
```

これと同じ内容を C# で書くと以下のようになります。

```csharp
new Page
{
  Content = new StackPanel
  {
    Children =
    {
      new TextBlock { Text = "Hello world" },
      new TextBlock { Text = "こんにちは世界" },
      new TextBlock { Text = "コレクション構文のサンプル" },
    }
  }
};
```

XAML も C# も似たような構文で記載できることがわかると思います。

## コンテンツプロパティ

各クラスには最大で 1 つのコンテンツ プロパティを持つことが出来ます。
Page クラスだと Content プロパティがコンテンツ プロパティとして設定されています(ややこしいですが…)。StackPanel は Children プロパティがコンテンツ プロパティとして設定されています。
コンテンツ プロパティは、タグの直下に書かれたタグの値が自動的に設定されます。つまり、ここまでのプロパティ要素構文とコレクション構文でしていたのように `<Page.Content>...</Page.Content>` や `StackPanel.Children>...</StackPanel.Children` は省略して書けます。

省略して書いた XAML を以下に示します。

```xml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <StackPanel>
        <TextBlock Text="Hello world" />
        <TextBlock Text="こんにちは世界" />
        <TextBlock Text="コレクション構文のサンプル" />
    </StackPanel>
</Page>
```

このように、よく利用するプロパティはコンテンツ プロパティとして設定されているので、すっきりと書くことが出来るようになっています。

## 添付プロパティ

画面にコントロールを配置するときに、パネルに対してどのようにコントロールを置くのか設定したい時があります。
例えば Canvas というパネルは X 座標と Y 座標指定でコントロールを置けますが、各コントロールには X 座標と Y 座標を設定するためのプロパティがありません。

他の UI フレームワークでの一般的な解決方法は、各コントロールに LayoutOption といったようなレイアウトに必要な情報を入れるプロパティがあり、Canvas 固有の設定情報は CanvasLayoutOption といったクラスで Top や Left といったプロパティを用意して座標を設定出来るようにするといったアプローチがあります。

```xml
<!-- 注意！！疑似コードです！動きません！！ -->
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <!-- このコードは実際には動きません。 -->
    <Canvas>
        <TextBlock Text="Hello world">
            <TextBlock.LayoutOption>
                <CanvasLayoutOption Top="10" Left="10" />
            </TextBlock.LayoutOption>
        </TextBlock>
    </Canvas>
</Page>
```

この方法でもレイアウトに限定すれば上手くいきますが、その他にコントロール自身には直接関係なくても付帯情報をコントロールに付けたいといったケースは一般的に考えられます。
実際に Uno Platform がベースにしてる UWP にも Tag という何でも入るプロパティがコントロールに定義されています。
Tag プロパティは何でも入るかわりに、誰がどんな用途で使っているかわからないため多用していると別の人によって知らないうちに上書きされたりといった問題が起きます。

コントロールに対して任意のプロパティを足す仕組みがあると、この問題は解決します。それが、ここで紹介する添付プロパティです。添付プロパティは属性に `クラス名.添付プロパティ名="値"` という形で指定する方法とプロパティ要素構文で `<クラス名.添付プロパティ名>値</クラス名.添付プロパティ名>` という形で指定する方法があります。
Canvas は Top と Left という添付プロパティを持っています。これを子要素のコントロールに指定することでレイアウトを設定します。TextBlock を 10, 10 の座標に設定する XAML は以下のようになります。

```xml:MainPage.xaml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Canvas>
        <TextBlock
            Text="Hello world"
            Canvas.Top="10"
            Canvas.Left="10" />
    </Canvas>
</Page>
```

このコードを実行すると以下のようになります。

![xaml-attached-property.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/31c3b8c8-0dd6-12f9-afbf-24104dfe04ae.png)

添付プロパティは C# では以下のように書きます。

```csharp
var textBlock = new TextBlock
{
    Text = "Hello world"
};

// textBlock に添付プロパティを設定
Canvas.SetTop(textBlock, 10);
Canvas.SetLeft(textBlock, 10);
```

XAML に比べると C# は冗長な書きかたになってしまいます。

### 余談：Xamarin.Forms の CSharpMarkup

最近の Flutter や SwiftUI の流れを汲んでかどうかは確かではないですが、Uno Platform と同じように XAML で UI を組む Xamarin.Forms で C# で UI を組むための CSharpMarkup という機能が追加されました。CSharpMarkup では添付プロパティは以下のように C# の拡張メソッドとして定義されていてメソッドチェインで書けるようになっています。

```csharp
// Label は Xamarin.Forms で文字列を表示するためのコントロール
new Label
{
    Text = "Hello world",
}.Row(1).Column(2).ColumnSpan(2);
```

これは以下の XAML と同じです。

```xml
<Label Text="Hello world"
  Grid.Row="1"
  Grid.Column="2"
  Grid.ColumnSpan="2" />
```

CSharpMarkup に興味を持った人は以下の CSharpMarkup の Spec を覗いてみてください。

[[Spec] CSharpForMarkup](https://github.com/xamarin/Xamarin.Forms/pull/8342)

## マークアップ拡張

最後にマークアップ拡張について紹介します。マークアップ拡張は、属性に対してオブジェクトを設定したり特別な値を設定するために使われます。
マークアップ拡張はタグの属性に `<タグ名 属性名="{マークアップ拡張名 コンストラクタ引数, プロパティ1=xxx, プロパティ2=yyy}" />` のように `{}` で囲って指定します。

マークアップ拡張を使用するとプロパティ要素構文より簡潔に値を指定できるので、よく使うものに関してはマークアップ拡張が提供されています。例としてリソースから値を取得する StaticResource マークアップ拡張を紹介します。
UWP のコントロールは Resources というプロパティがあり、そこに共通で使用する値やオブジェクトを x:Key 属性で名前を付けて登録できます。
その値を、取得するのが StaticResource マークアップ拡張で `{StaticResource キー名}` のように使います。

赤色のブラシをリソースに登録して、これを複数のコントロールから参照する例を以下に示します。

```xml:MainPage.xaml
<Page
    x:Class="HelloUnoPlatform.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloUnoPlatform"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <SolidColorBrush x:Key="AlertBlush" Color="Red" />
    </Page.Resources>
    <StackPanel>
        <TextBlock Text="Hello world" Foreground="{StaticResource AlertBlush}" />
        <TextBlock Text="こんにちは世界" Foreground="{StaticResource AlertBlush}" />
    </StackPanel>
</Page>
```

2 つの TextBlock の Foreground を Resources で定義している AlertBrush (赤色の単色ブラシ) にしています。この XAML を実行すると以下のように赤色のテキストが 2 つ表示されます。

![markup-extension.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/3bbbfaae-e4f5-59f2-5f05-04843b59a182.png)