C#アプリのGUIは専用のマークアップ言語であるXAMLを用いて構築することができます。XAMLを使えばGUI部品をプラモデルのように組み合わせていくことで簡単にGUIができるので、非常に有用です。同じくマークアップ言語であるHTMLでホームページを作るのと同じイメージで、XAMLを使ってアプリの外観を作れるといえばわかりやすいでしょうか?
ここでは、XAMLの基本的な文法について説明します。
[toc]XAMLとは
XAMLはCLRオブジェクトのインスタンスを生成するためのマークアップ言語です。XAMLの実体はXML言語でCLRオブジェクトとの対応付けのルールを定義されたものになっています。XML言語については以下の記事をご覧ください。
また、WinUI 3.0のクラスライブラリにおけるXAMLのCLRオブジェクトとの対応付けルールは「Microsoft.UI.Xaml.Markup 名前空間」もご参照ください。
.NETではC#やF#、VB.NETなどの様々な言語から成るプログラムを共通中間言語(CIL)に変換して、共通言語基盤(CLI)で実行しますがその主要な実装がCLRです。つまり、「CLRオブジェクト」とはC#以外にもF#やVB.NETなど各種プログラムから生成された共通オブジェクトのことを指しています。
XAMLとC#オブジェクトとの関係
名前空間の表現
デフォルトで指定されている名前空間
Visual StudioでUWPアプリを作成すると、デフォルトではXAMLのコードに次の名前空間が指定されています。なお、デフォルト名前空間として設定されているものについては、何も指定せずにその要素や属性を用いることができ、グローバル属性として設定されている名前空間については、「x:Class」のように接頭辞を用いてその要素や属性を用いる必要があります。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- WPFやUWPのUI要素を参照するための名前空間
- デフォルト名前空間として設定されています。
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- XAMLスキーマではなく、マッピングルールとしてのXAML自身の言語機能を提供する名前空間(XAML名前空間)
- この名前空間の主な属性には次のようなものがあります。
x:Class属性 | そのXAMLコードの表すクラス名を表し、C#コードの部分クラスと対応させます。 |
x:Key属性 | リソースを識別します。 |
x:Name属性 | C#コードからこのコントロールを識別するためのものです。 (C#のコードからは、このコントロールを参照している変数名として扱えます) ※「x:」接頭辞が付かないName属性は、そのコントロールのNameプロパティとなります。 |
x:Uid属性 | このコントロールを識別する一意識別子です。 ローカライズした文字列を割り当てるときに利用します。 |
x:Null | C#のnullに相当します。 |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- Visual Studio や Blend など、デザインツール上だけで反映される機能
- 「mc:Ignorable=”d”」とすることで、d で指定された名前空間を無視するように設定されているので、アプリ起動時には無視されます
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Open XML Formats の名前空間(特定の名前空間を無視するなど、XAMLでも一部の機能を使用しています)
追加で名前空間を指定する場合
C#のコードで名前空間を指定するのと同様にXAMLでも名前空間を指定することで、他のライブラリを参照したりすることが可能です。追加で名前空間を指定する場合は、xmlns属性をXMLのグローバル属性として定義します。ただしこの際、WPFアプリとUWPアプリ(WinUI)で名前空間の追加の方法が異なるので注意してください。
UWPアプリのコード
xmlns:〇〇="using:◇◇"
WPFアプリのコード
xmlns:〇〇="clr-namespace:◇◇"
となります。例えば、
using Windows.UI.Xaml.Controls.Maps;
として名前空間への参照を追加するとき、UWPのXAMLコードでは
xmlns:Maps="using:Windows.UI.Xaml.Controls.Maps"
となります。
Visual StudioのXAMLテンプレート
Visual StudioでUWPアプリを作成するとデフォルトでは以下の名前空間が追加されています。
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
</Grid>
</Page>
ここまでの説明で、このデフォルトコードの意味がだいぶ分かるのではないでしょうか?
2行目で部分クラスを宣言して、3-7行目で参照する名前空間を定義しています。さらに8行目で接頭辞”d”をアプリ実行時には無視するように設定しています。
クラスと構造体のオブジェクトの表現
オブジェクト要素構文
XAMLでCLRオブジェクトを表現する場合は、型名(クラスや構造体)をそのまま要素名にしたXML要素を記述することで、その型のオブジェクトが生成されます。この構文を「オブジェクト要素構文」と呼びます。
C#コード
var obj = new Class1();
XAMLコード
<Class1 x:Name="obj"/>
なお、この例ではx:Name属性を用いてオブジェクトを格納している変数名を指定していますが、静的なGUIを作成するだけであればオブジェクト名は不要な場合が多く、従ってx:Name属性で変数名を指定するのは必要な時だけで構いません。
プロパティとイベントの表現
XAMLではプロパティやイベントをXMLの属性やコンテンツの値として指定します。ここで、この値は文字列しか取りませんが、実際のプロパティの値は文字列以外にも様々なクラスのオブジェクトを取り得ます。そのため、属性の値として指定された文字列を適切なクラスのオブジェクトに変換する必要があり、その仕組みが型コンバーターとなります。
XAMLでのプロパティの指定の仕方には、属性構文・プロパティ要素構文やコンテンツプロパティがあります。
属性構文
XML属性でプロパティを表す方法を「属性構文」と呼びます。この構文を用いると、XML属性への値の代入がCLRオブジェクトのプロパティへの値の代入となります。
C#コード
var obj = new Class1();
obj.Text = "abcd";
XAMLコード
<Class1 Text="abcd" />
属性構文の応用としては以下のようなものがあります。
属性構文を用いたイベントの指定
属性構文を用いてイベントハンドラーにイベント処理を表すメソッドを登録することも可能です。なお、イベント処理自体はC#コードで記述する必要があります。例えば、ボタンをクリックしたときの処理としてButtonButton_Click_1というメソッドを登録してみましょう。
<Button Content="ボタン" Click="Button_Click_1" />
属性構文のマークアップ拡張と依存関係プロパティ
属性構文ではそのXML属性に何らかの値を代入しますが、特定の値の代わりに他の要素に与えられる値と連動させたりすることができます。この仕組みのことを依存関係プロパティと呼び、プロパティに代入する値を{}で囲みます。この{}で囲む書き方をマークアップ拡張と呼びます。
このマークアップ拡張を用いて記述した依存関係プロパティによって、データバインディングやリソース・スタイルなどを表せます。例えばマークアップ拡張を用いて表現したデータバインディングは次のようになります。
<TextBlock x:Name="textBlock" Text="{x:Bind Path=slider.Value, Mode=TwoWay}"/>
データバインディングについては次の記事をご覧ください。
プロパティ要素構文
属性構文ではXML属性でCLRオブジェクトのプロパティを表していましたが、プロパティ要素構文ではXML要素でCLRオブジェクトのプロパティを表します。具体的には「型名.プロパティ名」という名前のXML要素の開始タグと終了タグの間に値を指定することで、CLRオブジェクトのプロパティへの値の代入を表します。
C#コード
var obj = new Class1();
obj.Text = "abcd";
XAMLコード
<Class1>
<Class1.Text>"abcd"</Class1.Text>
</Class1>
プロパティ要素構文の応用としては以下のようなものがあります。
コレクション構文
「型名.プロパティ名」から成る開始タグと終了タグの間に複数のオブジェクト要素を並べることで、その複数のオブジェクト要素をコレクションとして追加することができます。この構文はプロパティ要素構文の一種ですが、特別にコレクション構文と呼びます。
C#コード
var obj = new Class1();
obj.Values = new List<int>() {1, 2, 3};
XAMLコード
<Class1>
<Class1.Values>
<x:Int32>1</x:Int32>
<x:Int32>2</x:Int32>
<x:Int32>3</x:Int32>
</Class1.Values>
</Class1>
なお、コレクション構文では、リストだけでなくディクショナリをプロパティに指定することもできます。この場合は、次のようにXAMLコードのx:Key属性にディクショナリのキーを指定します。
XAMLコード
<Class1>
<Class1.Values>
<x:Int32 x:Key="A">1</x:Int32>
<x:Int32 x:Key="B">2</x:Int32>
<x:Int32 x:Key="C">3</x:Int32>
</Class1.Values>
</Class1>
コンテントプロパティ
コンテントプロパティとして設定されたプロパティは、XAML要素の開始タグと終了タグの間に直接書くことでプロパティの値として設定できます。コンテントプロパティは型定義の際にContentProperty属性をつけることで設定でき、1つの型につきコンテントプロパティに設定できるプロパティは1つだけです。
例えば、Buttonクラスはその継承元のContentControlクラスの定義で以下のようになっていて、
[Microsoft.UI.Xaml.Markup.ContentProperty(Name="Content")]
...(略)...
public class ContentControl : Control
Contentプロパティがコンテントプロパティとして設定されていることが分かります。ButtonクラスにおけるContentプロパティはボタンのラベルに相当するので、
<Button>Button1</Button>
というように、開始タグと終了タグの間に直接表示したい文字列を書き込むだけでボタンのラベルの設定をできます。なお、これはプロパティ要素構文や属性構文を用いてそれぞれ次のように表すことができます。
<!--プロパティ要素構文--!>
<Button>
<Button.Content>Button2</Button.Content>
</Button>
<!--属性構文--!>
<Button Content="Button3"/>
添付プロパティ
添付プロパティを用いることで親要素が保持する子要素に関する情報を、子要素側から指定することができます。子要素のタグ内で「型名.添付プロパティ名」という名前のXML属性に値を代入することで、その要素の親要素の持つプロパティへ値を代入したことになります。
添付プロパティの代表的な使用箇所はGridレイアウトにおける要素の配置の指定方法などです。
XAMLコード
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="1行目のボタン" Grid.Row="0" />
<Button Content="2行目のボタン" Grid.Row="1" />
</Grid>
アクセス修飾子の表現
XAMLで生成するオブジェクトのアクセス修飾子はデフォルトでinternalとなっていますが、x:FieldModifier属性を用いてアクセス修飾子を変更可能です。
例えば、アクセス修飾子をprotectedとしたTextBlockを生成する場合は以下のようになります。
<TextBlock x:FieldModifier="protected"/>
コメント