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}
とかあの辺りについてとかです。まったり頑張りましょう。