Posted at

Livetで始めるWPF(ざっくり)入門 その4

More than 3 years have passed since last update.


XAMLとは

今回はXAMLについてです。初回でXAMLとはHTMLみたいなものだと言いました。また、AltJSのようなものだとも言いました。まず、XAMLはXML拡張です。ので、基本的にはXMLです。そして、XAMLはC#にコンパイルされます。HTMLのようにレンダリングエンジンが直接XAMLを解釈とかは(基本的には)しません1

XAML編は、XAMLがどんな感じでC#にコンパイルされるのか、というXAML基本編と、XMLにはないXAML独自の拡張機能について、というXAML応用編の2本立てです。今回は基本編のみで、応用編は次回です。


XAML変換ルール

以下の様なXAMLを考えます。

<Hoge></Hoge>

これはこんな感じのC#コードにコンパイルされます。

var hoge = new Hoge();

あくまでこんな感じ、です。オブジェクトの実際の名前とかはこの限りではないです。

さて、ここからわかったこととして、XAMLの要素はC#のクラスに相当します。そして、XAMLでタグを書く事は、そのクラスのオブジェクトをnewすることに相当します。あ、XML拡張なので<Hoge/>と書いても大丈夫です。どっちも同じ意味です。

お次は属性です。

<Hoge Piyo="123" Fuga="abc"></Hoge>

これは次のようなC#コードに変換されます。

var hoge = new Hoge()

{
Piyo = "123",
Fuga = "abc"
};

つまり、XAMLの属性はC#のプロパティに相当します。また、プロパティはsetterを持っている必要もあります。そして、名前空間です。

<Hoge xmlns="clr-namespace:MyProject"></Hoge>

変換されるC#コードは以下のようになります。

var hoge = MyProject.Hoge()

XAML名前空間はそのままC#の名前空間に相当します。ただ、XAMLにはXMLから継承したXML名前空間もありますので、そちらと区別して.NETの名前空間を参照したい場合は、上記のようにclr-namespace:が必要です。

ここまでが基本です。今のところはなかなかシンプルなルールです。次辺りから雲行きが怪しくなります。


入れ子の解釈

XAMLはXML拡張ですので入れ子構造を持ちます。つまり、以下の様な場合です。

<Hoge>

<Piyo/>
</Hoge>

このような入れ子要素は何らかの属性への設定と見なされます。つまり、以下の様に解釈されます。

<Hoge ???=<Piyo/>>

</Hoge>

もちろんこれは擬似コードです。まず、XAMLで入れ子要素を受け付けるクラスは、ContentProperty属性を付け、対象となるプロパティを指定する必要があります。例えば、Hogeクラスは以下のように定義されていたとしましょう。

[ContentProperty("Fuga")]

public class Hoge
{
public Piyo Fuga { get; set; }
}

この場合、最初のXAMLは以下の様なC#コードとして解釈されます。

var hoge = new Hoge()

{
Fuga = new Piyo()
};

つまり、XAMLはひたすらオブジェクトを構築していくC#コードを表現しています。ちなみに、最初のXAMLはより明示的に書くこともできます。

<Hoge>

<Hoge.Fuga>
<Piyo/>
</Hoge.Fuga>
</Hoge>

このXAMLコードも先ほどのC#コードを生成します。この場合、ContentPropertyは不要です。つまり、ContentProperty属性とは、このような冗長な記法に対するシンタックスシュガーです。まあ、よりXMLライクに書けるための配慮ですね。

また、属性の説明で示した<Hoge Piyo="123" Fuga="abc"></Hoge>というXAMLコードですが、実はこれは以下のように書き直せます。

<Hoge xmlns:s="clr-namespace:System">

<Hoge.Piyo>
<s:String>123</s:String>
</Hoge.Piyo>
<Hoge.Fuga>
<s:String>abc</s:String>
</Hoge.Fuga>
</Hoge>

xmlns:s=Systemというのはusing s = System;と同じです。つまり名前空間のエイリアスですね。また、<s:String/>new s.String()と同じです。つまりnew System.String()ですね。

XAMLを最大限一般化した形が上記のようになります。ただ、文字列についてはx="abc"といったようにXAML上でリテラルが用意されているので、<String>abc</String>と書く必要はありません2

文字列以外だとリテラルがないので<Hoge.Piyo>みたいなものを入れ子にする必要があります。実際は簡単なものならコンバータを駆使して云々するんですが、そんな面倒なことは今はどうでもいいことです。


複数の入れ子

複数の入れ子も表現可能です。ただ、その場合ContentProperty属性が指定するプロパティがコレクション初期化子の対象である必要があります。つまり、IEnumerableを実装していてAddメソッドを持っているやつです。なので、変換されるC#コードはコレクション初期化子を用いたものになります。

ただ、コレクション初期化子を用いなくても、コレクション自体を明示的に指定してやれば可能です。この辺りは文字だけで説明してもなんのこっちゃという感じだと思うので実際に見てみましょう。例えば以下の様なHogeクラスを考えます。

[ContentProperty("Children")]

public class Hoge
{
readonly List<Piyo> children = new List<Piyo>();
public List<Piyo> Children
{
get { return children; }
set { children = value; }
}
}

すると以下の様なXAMLが書けます。

<Hoge>

<Piyo/>
<Piyo/>
</Hoge>

しかし、Childrenプロパティの型がIEnumerable<Piyo>の場合は、設定するコレクションオブジェクトを明示的に書く必要があります。

<Hoge xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<List xmlns="clr-namespace=System.Collections.Generic" x:TypeArguments="Hoge">
<Piyo/>
<Piyo/>
</Hoge.Children>
</Hoge>

x:TypeArguments="Hoge"は型パラメータの指定です。つまりList<Hoge>を表現しています。あ、もちろんList<Hoge>じゃなくても問題無いです。IEnumerable<Hoge>に適合すれば良いだけです。

ただ、大抵のコントロールのコレクションプロパティはコレクション初期化子に対応しているので特に気を付ける必要はないと思います。


XAMLで遊ぶ

XAMLの変換ルールは慣れるまでが大変です。まあ、Blendというツールを使えばXAMLを直接いじる必要はあんまりないんですが、やっぱりWPFの中核を担う技術ですし、知ってたほうが良いんじゃないかと思います。というわけで、ひたすら手を動かして慣れましょう。

XAML技術自体はWPFと独立したただのXML拡張マークアップ言語です。XamlServices.Parseメソッドを使うと普通にパースできます。というわけでやってみましょう。

まず、System.Xaml.dllが必要なのでWPFアプリで試す必要があります。もちろんコンソールアプリから別途System.Xaml.dllを参照しても良いです。面倒な人はLivetプロジェクトのMainWindowViewModelで試せば良いんじゃないでしょうか。

その次に、まだ説明してないこととして、XAMLの名前空間で.NET名前空間を指す場合clr-namespace:が必要だと言いましたが;;assembly=xxxという形でアセンブリも書く必要があります。WPFの場合は必要なアセンブリがもう既に参照されていることをXAMLパーサが理解しているので不要ですが、XamlServices.Parseでパースする場合はmscorlibでさえも明示的なアセンブリ指定が必要です。

また、ユーザー定義クラスをXAMLに書く場合、publicである必要があります。これは、.NETオブジェクトを直接生成するため、System.Xaml.dllから可視である必要があるためです。

というわけで、こんな感じです。

<p:Hoge xmlns:p="clr-namespace:Sample;assembly=Sample" Piyo="hello, world">

<p:Hoge/>
<p:Hoge>
<p:Hoge.Piyo>
<String xmlns="clr-namespace:System;assembly=mscorlib">
hogepiyo
</String>
</p:Hoge.Piyo>
</p:Hoge>
</p:Hoge>

using System.Xaml;

using System.Windows.Markup;

namespace Sample
{
public class Hoge
{
public string Piyo { get; set; }

List<Hoge> children = new List<Hoge>();
public List<Hoge> Children
{
get { return children; }
set { children = value; }
}

public static void Test(string xaml)
{
var result = XamlServices.Parse(xaml);
// 以下と等価
var result2 = new Hoge()
{
Piyo = "hello, world",
Children =
{
new Hoge(),
new Hoge()
{
Piyo = "hogepiyo"
}
}
};
}
}
}

こんな感じでお手軽に(?)試せますので、疑問点等あれば試してみると良いんじゃないでしょうか。また、今までの記事で示したViewについてもある程度読めるようになっているかと思うので、見返してみるのもいいかもしれません。が、まだ全部は読めないと思いますので、読める範囲で大丈夫です。


次回予告

もう全然Livetとか出てないのでタイトル詐欺もいいとこですね。

冒頭でも述べたように、次回はXAML応用編です。例えばデータバインディングでよく出てきた{Binding}とかあの辺りについてとかです。まったり頑張りましょう。


注釈





  1. 厳密にはIEとかはXAMLを直接解釈、実行できます。ただ、その場合は内部で独自のC#コードを書いてはいけません。このようにIE等で独立して実行可能なXAMLをLoose XAMLとか呼びますが、まあ、どうでもいいですね。 



  2. 文字列をいくつかのXAMLで使い回したいみたいな時に、変数みたいな感じでやることはあります。