第2回 WPFとXAMLの関係とは? XAMLの基礎を学ぶ:連載:WPF入門(3/3 ページ)
WPFアプリケーション開発において実質的にはXAMLに関する知識が必須。WPFとXAMLの関係性をコード例で分かりやすく紹介。XAML構文をまとめる。
■XAML構文のまとめ
●XML名前空間
XML名前空間には、以下のようにCLR名前空間とアセンブリ名に関する情報を書く。
xmlns:ns="clr-namespace:CLR名前空間名;assembly=アセンブリ名"
以後、<ns:クラス名>というようなXML要素を書くことで、対応するCLRオブジェクトのインスタンスを生成できる(当然ながらクラス名は、記述したアセンブリに含まれるCLR名前空間のクラスでなければならない)。
ただし、WPFの場合には関連するクラスがさまざまな名前空間にわたって定義されているが、これらを逐一XML名前空間に書く必要はなく、以下のようなWPF専用のXML名前空間を1つ書くだけで構わない。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
また、XAML固有の機能を使うために、通常は以下のXML名前空間も併せて記述する。
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
●属性構文
XAMLコード中のXML属性は、CLRオブジェクトのプロパティもしくはイベントへの値の代入として解釈される(「属性構文(attribute syntax)」と呼ぶ)。プロパティの場合、XML属性値(文字列)から所望の型への変換は、以下の手順で行われる。
(1)まず、CLRオブジェクトのプロパティにTypeConverter属性が付いているかどうかを調べ、付いている場合はこの属性情報に基づいた型コンバータを使って、ConvertFromメソッドにより値を生成する
(2)プロパティの次に、プロパティの型に対してTypeConverter属性が付いているかどうかを調べ、同様にこの情報に基づいて値を生成する
イベントの場合には、分離コード内にそのイベント・ハンドラとなるメソッドが定義されている必要がある。
●プロパティ要素構文
文字列からの変換が困難であるような複雑なインスタンスを生成する場合は、XML属性の代わりに、子要素としてプロパティ値を設定することもできる(「プロパティ要素構文(property element syntax)」と呼ぶ)。プロパティ要素構文では、<型名.プロパティ名>という形のXML要素を記述する。
属性構文とプロパティ要素構文とで得られる結果は同じで、例えば、List 13の2つのButton要素は同じ構造のインスタンスを生成する(値を文字列だけで書ける場合には属性構文、そうでない場合にプロパティ要素構文の利用が推奨される)。
<Button Background="Blue" />
<Button>
<Button.Background>Blue</Button.Background >
</Button>
●マークアップ拡張
プロパティ要素構文に加えて、属性構文を用いて複雑なインスタンスを生成する手段として、「マークアップ拡張(markup extension)」と呼ばれる機能がある。詳細は次回以降で解説することになるが、データ・バインディングを行うためのBindingマークアップ拡張がその代表例だ(List 14)。
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="100">
<Slider Name="slider" />
<TextBox Text="{Binding ElementName=slider, Path=Value}" />
</StackPanel>
データ・バインディングにより、スライダ・コントロールの値(=Valueプロパティの値)がテキストボックスに反映される。
マークアップ拡張では、XML属性中に { } で囲った記述を書くと、対応するマークアップ拡張クラスのインスタンス生成と、そのProvideValueメソッド呼び出しを通してプロパティの値が設定される。List 14の場合、List 15に示すようなBindingクラスのインスタンスが生成され、Binding.ProvideValueメソッド内でデータ・バインディングの処理が行われる。
var binding = new System.Windows.Data.Binding
{
ElementName = "slider",
Path = new PropertyPath("Value"),
};
Dim binding = New System.Windows.Data.Binding With
{
.ElementName = "slider",
.Path = new PropertyPath("Value")
}
マークアップ拡張クラスは自作することもできる。
- マークアップ拡張クラスは、MarkupExtensionクラス(System.Windows.Markup名前空間)を継承する必要がある
- 名前の末尾に「Extension」が付く場合、XAMLコード中では「Extension」の部分を省略できる(例えば「ArrayExtension」は「Array」と記述できる。なお、一般的なクラスの命名規則としては「Extension」を付ける。Bindingクラス(System.Windows.Data名前空間)はこの例には当てはまらない)
●コンテンツ・プロパティとXML要素の省略
XAMLには、XAMLコードの可読性を向上させるため、XML要素の省略機構がいくつか存在する。その1つがコンテンツ・プロパティ(content property)で、クラスに対してContentProperty属性が付いている場合、その情報に基づいてXML要素の省略が可能になる。
例えば、Buttonクラスの場合、親クラスであるContentControlクラスに[ContentProperty("Content")]属性が付いているため、<Button.Content>要素を省略できる。すなわち、List 16に示す2つの<Button>要素は同じ意味になる。
<Button>
<Button.Content>ボタン</Button.Content>
</Button>
<Button>
ボタン
</Button>
●コレクション型の省略
プロパティ要素構文で、プロパティの値がコレクションの場合、コレクションに相当するXML要素を省略することができる。例えば、StackPanelクラスなどのChildrenプロパティ(=コンテンツ・プロパティに設定されている)の型はUIElementCollectionというコレクション・クラスであるため、省略可能である。List 17に示す2つの<StackPanel>要素は同じ意味になる。
<StackPanel>
<StackPanel.Children>
<UIElementCollection>
<Button Content="ボタン1" />
<Button Content="ボタン2" />
</UIElementCollection>
</StackPanel.Children>
</StackPanel>
<StackPanel>
<Button Content="ボタン1" />
<Button Content="ボタン2" />
</StackPanel>
省略可能となるのは以下のいずれかの場合である。
- IListインターフェイス(ジェネリック版/非ジェネリック版ともに)を実装するクラス
- IDictionaryインターフェイス(ジェネリック版/非ジェネリック版ともに)を実装するクラス(この場合、子要素にx:Key属性が必須となる)
- 配列
もう1つ、IAddChildインターフェイスによるコレクション型要素の省略も可能であるが、過去の互換性のためだけにあり、新規に利用することはないだろう。
●添付プロパティ
繰り返しになるが、XAMLには、自分自身ではなく親要素で用いる値を保持するための機構として添付プロパティ(attached property)という機能が存在する。例えば「<Button Canvas.Left="10" />」というように、XML属性名に「型名.添付プロパティ名」を指定する。このような添付プロパティの値の設定は、「型名.Set添付プロパティ名」という名前の静的メソッド呼び出しとして解釈される。前記の「Canvas.Left」の例であれば、「Canvas.SetLeft(button, 10)」というようなメソッド呼び出しになる。
ちなみに、WPFの内部的には、次回以降で説明する依存関係プロパティ(dependency property)というものを用いて添付プロパティを実装している。Canvas.SetLeftメソッドの例でいうと、内部的にはList 18に示すような実装になっている。
public class Canvas
{
public static readonly DependencyProperty LeftProperty
= // 詳細は省略
public static void SetLeft(DependecyObject element, double value)
{
element.SetValue(LeftProperty, value);
}
}
Public Class Canvas
Public Shared ReadOnly LeftProperty As DependencyProperty
= ' 詳細は省略
Public Shared Sub SetLeft(ByVal element As DependencyObject, ByVal value As Double)
element.SetValue(LeftProperty, value)
End Sub
End Class
添付プロパティと同様に、イベントに関しても親子間でのイベントの伝搬を行うルーティング・イベント(routed event)という機能があり、添付プロパティと同様に、「型名.イベント名="イベント・ハンドラ"」という書き方ができる。ルーティング・イベントについては次回以降で説明を行う。
●XML形式
XAMLの記述にはXML形式を用いるため、XML形式を使ううえでの注意点がそのままXAMLにも当てはまる。
- 「<」「>」「&」「"」などの文字はそのままでは利用できない
- それぞれ、「<」「>」「&」「"e;」という記述に置き換える必要がある
- あるいはCDATAセクションを用いる(「<![CDATA[」と「]]>」で囲む)
- 空白文字は無視される
- 空白文字に意味を持たせるためには、XML要素に「xml:space="preserve"」という属性を追加する
[参考]XAMLコード単体でUI(=ユーザー・インターフェイス)表示
最後に、余談になるが、XAMLコード単体で(C#などの分離コードを一切書かず、MSBuildによるコンパイルも行わず)UIを表示させる方法に触れておこう。
●Loose XAML
分離コードを必要としない(あるいはXAMLコード中に、<x:Code>要素によりインラインでC#/VBのプログラミング・コードを埋め込んでいない)XAMLコードであれば、コンパイルせずにそのままIE(Internet Explorer)などのブラウザ上で表示することが可能である。この、C#/VBなどのプログラミング・コードを含まないXAMLコードを「Loose XAML」と呼ぶ。
Loose XAMLの表示は.NET Framework同梱のブラウザ・プラグインによるものであり、.NET Framework 3.0のインストールされたWindows上でならIEのみ、3.5以上ならIEに加えてFirefoxでの表示が可能だ。
●Silverlight
WPFではなくSilverlightを用いた技術になるが、「Gestalt」というライブラリを使うことでHTMLコード中にXAMLコードを埋め込んで表示できる。Silverlightベースであるため、WPFとは利用できるUI要素などに差があるが、<Button>要素などの基本的なものはそのまま利用できる部分も多い。以下の例では、上述のLoose XAMLとほぼ同じ(<Menu>要素と<StatusBar>要素がない以外はまったく同じ)XAMLコードをHTMLコード中に埋め込んでいる。
次回はWPF固有の機能を交えつつ、XAMLの説明を行っていく。
「連載:WPF入門」
Copyright© Digital Advantage Corp. All Rights Reserved.