第2回 WPFとXAMLの関係とは? XAMLの基礎を学ぶ:連載:WPF入門(2/3 ページ)
WPFアプリケーション開発において実質的にはXAMLに関する知識が必須。WPFとXAMLの関係性をコード例で分かりやすく紹介。XAML構文をまとめる。
■XAMLの基礎
今回は特に、WPFに依存しない「インスタンス生成用のマークアップ言語」としてのXAMLを中心に説明する。
●XAMLのXML要素/属性と、対応するCLRオブジェクト
詳細について話す前にまず、XAMLコードと、それとほぼ等価のCLRオブジェクトを生成するC#/VBコードについていくつかの例を挙げることで、大まかなイメージをつかんでもらいたい。List 4〜List 10にその例を示す。
●ユーザー定義クラスのインスタンス生成例
List 4は、(XAMLコードにおける<Point>要素から)ユーザー定義クラス「Point」のインスタンス(=CLRオブジェクト)が生成される例である。
<Point X="1" Y="2"
xmlns="clr-namespace:Sample;assembly=SampleLibrary" />
var obj = new Sample.Point
{
X = 1,
Y = 2
};
Dim obj = New Sample.Point With
{
.X = 1,
.Y = 2
}
大まかにいうと、以下のような対応関係になる。
- XML名前空間(xmlns属性) = CLR名前空間 + アセンブリ情報
- XML要素名 = CLRクラス名
- XML属性 = CLRオブジェクトのプロパティ値設定(属性構文)
●System.Uriクラスのインスタンス生成例
次のList 5は、(XAMLコードにおける<Uri>要素から)Uriクラス(System名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。
<Uri xmlns="clr-namespace:System;assembly=System">
http://www.atmarkit.co.jp/</Uri>
var converter = new System.UriTypeConverter();
var obj = converter.ConvertFrom("http://www.atmarkit.co.jp/");
// ↑System.Uriクラス(=<Uri>要素)には
// [TypeConverter(typeof(UriTypeConverter))]属性が付いている
Dim converter = New System.UriTypeConverter()
Dim obj = converter.ConvertFrom("http://www.atmarkit.co.jp/")
' ↑System.Uriクラス(=<Uri>要素)には
' <TypeConverter(GetType(UriTypeConverter))>属性が付いている
XAMLコード中の各要素の文字列は、CLRクラスに付与されたTypeConverter属性の値に基づいて変換処理が行われる。例えばList 5のXAMLコードにある<Uri>要素の文字列は、Uriクラスに付与されたTypeConverter属性の「UriTypeConverter」に基づき、C#やVBコードにおけるUriクラスのインスタンス(=CLRオブジェクト)に変換される。
●System.Windows.Controls.Buttonクラスのインスタンス生成例
List 6は、(XAMLコードにおける<Button>要素から)Buttonクラス(System.Windows.Controls名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。
<Button
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
FontSize="20"
Background="Blue">
ボタン
</Button>
var obj = new Button
{
Content = "ボタン",
// ↑Buttonクラス(の親クラスであるContentControlクラス)には
// [ContentProperty("Content")]属性が付いている
FontSize = 20,
Background = new SolidColorBrush(Colors.Blue),
// ↑BrushConverter.ConvertFrom("Blue") の結果。
// Backgroundプロパティの型であるBrushクラスには
// [TypeConverter(typeof(BrushConverter))]属性が付いている
};
' Buttonクラス(の親クラスであるContentControlクラス)には
' [ContentProperty("Content")]属性が付いている。
Dim obj = New Button With
{
.Content = "ボタン",
.FontSize = 20,
.Background = New SolidColorBrush(Colors.Blue)
} ' ↑BrushConverter.ConvertFrom("Blue") の結果
' Backgroundプロパティの型であるBrushクラスには
' <TypeConverter(GetType(BrushConverter))>]属性が付いている
List 6を見ると分かるように、WPF関連のクラスに関しては、XML名前空間の宣言に逐一「CLR名前空間+アセンブリ情報」を記述する必要はなく、WPF専用のXML名前空間「http://schemas.microsoft.com/winfx/2006/xaml/presentation」が用意されているので、代わりにこれを宣言すればよい。
また、CLRクラスにContentProperty属性を付けることで、その属性に指定されたプロパティは、
<Button Content="ボタン" />
のように属性ではなく、
<Button>ボタン</Button>
のように要素のテキスト・セレクションとして記述できる。
●複雑なプロパティ値を持つインスタンス生成例
次のList 7も、(XAMLコードにおける<Button>要素から)Buttonクラス(System.Windows.Controls名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。この例では、先ほどのList 6の場合よりも複雑なプロパティを持っている。
<Button
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0" />
<GradientStop Color="Blue" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
ボタン
</Button>
var obj = new Button
{
Content = "ボタン",
Background = new LinearGradientBrush
{
StartPoint = new Point { X = 0, Y = 0 },
EndPoint = new Point { X = 1, Y = 1 },
GradientStops =
{
new GradientStop { Color = Colors.White, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 },
}
}
};
Dim obj = New Button With
{
.Content = "ボタン",
.Background = New LinearGradientBrush With
{
.StartPoint = New Point With {.X = 0, .Y = 0},
.EndPoint = New Point With {.X = 1, .Y = 1},
.GradientStops = New GradientStopCollection From
{
New GradientStop With {.Color = Colors.White, .Offset = 0},
New GradientStop With {.Color = Colors.Blue, .Offset = 1}
}
}
}
文字列からの型変換では生成しにくいような複雑なプロパティ値を指定したい場合、XML属性ではなく、XML要素の形でプロパティ値を代入することも可能である(これは「プロパティ要素構文」と呼ばれる。List 7のXAMLコードでは、<Button.Background>要素などがその例)。
●マークアップ拡張を使ったプロパティ値設定の例
List 8は、(XAMLコードにおける<TextBlock>要素から)TextBlockクラス(System.Windows.Controls名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。
<TextBlock
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Foreground="{x:Static Member=SystemColors.HighlightTextBrush}"
Background="{x:Static Member=SystemColors.HighlightBrush}"
Text="text" />
var obj = new TextBlock
{
Text = "text",
Foreground = SystemColors.HighlightTextBrush,
Background = SystemColors.HighlightBrush,
};
// ↑実際には、以下のようなクラスのインスタンスが作られたうえで、
// そのインスタンスのProvideValueメソッドが呼ばれた結果、
// 静的メンバが取得される
//
// var extension = new StaticExtension
// {
// Member = "SystemColors.HighlightBrush"
// };
Dim obj = New TextBlock With
{
.Text = "text",
.Foreground = SystemColors.HighlightTextBrush,
.Background = SystemColors.HighlightBrush
}
' ↑実際には、以下のようなクラスのインスタンスが作られたうえで、
' そのインスタンスのProvideValueメソッドが呼ばれた結果、
' 静的メンバが取得される
'
'Dim extension As New StaticExtension With
'{
' .Member = "SystemColors.HighlightBrush"
'}
属性構文を使って複雑なインスタンスを生成するために、「マークアップ拡張」と呼ばれる機能も提供されている。XML属性の値として、{ } で囲った記述を行うと、一度MarkupExtensionクラスを継承したクラス(=List 8の例ではStaticExtensionクラス)のインスタンスが生成され、そのインスタンスのProvideValueメソッドの戻り値がプロパティの値として設定される。List 8の例では、何らかのクラスの静的プロパティを読み出すためのマークアップ拡張である「x:Static」を利用している。
●リストのインスタンス生成例
List 9は、(XAMLコードにおける<StackPanel>要素から)StackPanelクラス(System.Windows.Controls名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button Content="ボタン1" />
<Button Content="ボタン2" />
<Button Content="ボタン3" />
</StackPanel>
var obj = new StackPanel
{
Children =
// ↑StackPanelクラス(の親クラスであるPanelクラス)には
// [ContentProperty("Children")]属性が付いている
{
new Button { Content = "ボタン1" },
new Button { Content = "ボタン2" },
new Button { Content = "ボタン3" },
}
};
Dim obj As New StackPanel
obj.Children.Add(New Button With {.Content = "ボタン1"})
obj.Children.Add(New Button With {.Content = "ボタン2"})
obj.Children.Add(New Button With {.Content = "ボタン3"})
' ↑StackPanelクラス(の親クラスであるPanelクラス)には
' [ContentProperty("Children")]属性が付いている
StackPanelクラスのChildrenプロパティの型はUIElementCollectionクラス(System.Windows.Controls名前空間)だが、このようなIListインターフェイスを実装するクラスや配列の場合、<UIElementCollection>というようなリストを表すタグを1段省略できる。実際にList 9では、<UIElementCollection>タグが省略され、複数の<Button>要素が<StackPanel>タグの中に直接記述されている。
●添付プロパティを持つインスタンス生成例
List 10は、(XAMLコードにおける<Canvas>要素から)Canvasクラス(System.Windows.Controls名前空間)のインスタンス(=CLRオブジェクト)が生成される例である。
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="100" Height="100">
<Button Content="ボタン" Canvas.Left="10" Canvas.Top="50" />
</Canvas>
var button = new Button { Content = "ボタン" };
Canvas.SetLeft(button, 10);
Canvas.SetTop(button, 50);
var obj = new Canvas
{
Width = 100,
Height = 100,
Children = { button }
// ↑Canvasクラス(の親クラスであるPanelクラス)には
// [ContentProperty("Children")]属性が付いている
};
Dim button = New Button With {.Content = "ボタン"}
Canvas.SetLeft(button, 10)
Canvas.SetTop(button, 50)
Dim obj As New Canvas With
{
.Width = 100,
.Height = 100
}
obj.Children.Add(button)
' ↑Canvasクラス(の親クラスであるPanelクラス)には
' [ContentProperty("Children")]属性が付いている
添付プロパティ(=前回説明したように、自分自身ではなく、親要素で用いる値を保持するための機構)は「クラス名.Setプロパティ名」という名前の静的メソッド呼び出しとして解釈される。List 10の赤字の部分が、添付プロパティである。
以上、XAMLコードのXML要素と、それに対応するCLRオブジェクトの例を示した。
●分離コードの利用例
ここまではXAML単体で書ける仕様だったが、下記の記述例(C#/VB)ではMSBuildを通すのが必須になる。List 11にその例を示す。
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sample.MyGrid">
<Button
x:Name="button1"
Content="ボタン"
Click="button1_Click" />
</Grid>
using System.Windows.Controls;
using System.Windows;
namespace Sample
{
public partial class MyGrid : Grid
{
public MyGrid()
{
InitializeComponent();
}
void button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
MessageBox.Show("ボタンが押されました");
}
}
}
Namespace Sample
Class MyGrid
Inherits Grid
Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
MessageBox.Show("ボタンが押されました")
End Sub
End Class
End Namespace
「http://schemas.microsoft.com/winfx/2006/xaml」というXML名前空間は、(WPFに限らない)XAML全般に使う特別なスキーマを定義する。x:Class属性やx:Name属性もその一種で、x:Class属性には分離コードで実装するクラス名を、x:Name属性にはフィールド名を指定する。
また、プロパティと同様に、イベントに対しても「Click="button1_Click"」というような、XML属性を使った記述が可能である。そこで記述したメソッド名のイベント・ハンドラは、分離コード内で定義する必要がある。
●List 10およびList 11の組み合わせたC#/VBのみのコード
List 11の例のMyGridクラスとほぼ同等のものをC#/VBだけで記述すると、List 12のようになる。
using System.Windows.Controls;
using System.Windows;
namespace Sample
{
public partial class MyGrid : Grid
{
internal Button button1;
public MyGrid()
{
button1 = new Button { Content = "ボタン" };
button1.Click += button1_Click;
}
void button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
MessageBox.Show("ボタンが押されました");
}
}
}
Namespace Sample
Class MyGrid1
Inherits Grid
Friend button1 As Button
Public Sub New()
InitializeComponent()
button1 = New Button With {.Content = "ボタン"}
AddHandler button1.Click, AddressOf button1_Click
End Sub
Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
MessageBox.Show("ボタンが押されました")
End Sub
End Class
End Namespace
分離コードを用いたWPFアプリケーション作成に関して、詳細は次回以降で説明する。
最後に、これまでの例で出てきた構文についてまとめていこう。
Copyright© Digital Advantage Corp. All Rights Reserved.