XAML: Templateについて その1
やっとこさ来た XAML におけるデザイン作りこみの要、Template についてちょこちょこまとめる。
正直まとめるの大変な内容なので複数にわける。今回は「ItemsPanelTemplate」と「DataTemplate」だけ。
目次
Template は複数いる。全部じゃないと思うけど、自身が知った、知りたいものをまとめる。 Template の中で最も単純で簡単な Template である。これは、ItemsControl という複数のアイテムがソースになっているものの表示の外観を変更するものである。ここでは ListBox を例に書いていく。 ListBox はリストを表示するものである。
例えば、0~100 までのインデックスを持つ「itemN (N はindex番号)」のうち、偶数だけ表示するといったやつを載せる。
xamlコードはこんな感じ。 アイテムを作ってるクラスはこんな簡単なのを用意した。(さすがに100個も ListBoxItem は書いてられない。)
これを window.resources にインスタンス作ってやって、xaml 側でバインディングしている。 ここで、「2列とか3列にしたい」といったリスト表示の外観だけ変更したいときに「ItemsPanelTemplate」を使う。
そんな機会あるんかと言われたらそんなないかもだけど。しかし、ListBoxはアイテムの選択ができるため、「選択してこちょこちょしたいけどUIが。。。」なんてことがあるかもしれない。 やり方は簡単。xaml 側を下記のとおり変更する。3列に変更したものになる。 実行すると以下のとおり。
サンプルは UniformGrid を使用したが、別に Panel の派生クラスなら何でもよい。
StackPanel でも別にいい。なんだったら自作のクラスでもいける。Panel の派生クラスならよいためだ。 DataTemplate は前述したとおり、必ずしも外観が必要ないものにたいして外観を付け加えることができる。 Button とグラデーションオブジェクトを使って説明する。 以下のような xaml コードを考える。 これは、Button の Content に LinearGradientBrush というグラデーションオブジェクトを割り当てている。
Button は ContentControl の派生クラスであり、そのプロパティ Content は object 側のため、こういったオブジェクトならなんらエラーなく割り当てることができる。 しかし、これを実行してもグラデーションは出てこない。なんせグラデーションオブジェクトには外観がないため、ただの ToString したものが返ってくるからだ。つまり、LinearGradientBrush の型名が出てくるだけである。 そこで、DataTemplate で外観を与えるわけである。
今回は Ellipse を与える。 この xaml コードの実行結果は以下のとおり。
ちゃんとグラデーションがついた Ellipse が表示されていることがわかる。 ここで、Ellipse のプロパティ Fill へのバインディングに注目する。バインディングソースを一切指定していないのに特徴がある。
これは、ContentTemplate の DataContext に Button の Content が割り当てられているためである。つまり、既定のバインディングソースが割り当てられている状態、というのが正しい表現だろうか。 この DataTemaplate はリソースとしてインスタンスを作成できる(もちろん、ItemsPanelTemplate もできる)。つまり、1つの外観を複数のボタンに割り当てるといったことが可能になる。
一つ知識として知っておきたいのは、割り当てられたインスタンス1個1個に対してオブジェクトが作成されることである。 つまり、今回のような Ellipse のDataTemplate を 100個のボタンに割り当てると、100個の Ellipse が作成されるということである。
てっきり1オブジェクトに対して参照関係を作ってくれていると思っていたがそうではないらしいので注意がいる。 ちなみに、DataTemplate 単体でリソースにインスタンスを確保する機会は少ない(はず)。なぜなら、DataTemplate は Style に含めてインスタンス化できるためである。Style についてはまたいつかまとめる。ControlTemplate を触ればどのみち書くことになる。 非常に簡単な使い方を書いたけれども、DataTemplate を用いて外観に手を加えることも可能である。つまり、ControlTemplate みたいな使い方である。 ItemsPanelTemplate でも紹介した ListBox に対して、「罫線のように各アイテムを線で囲む」という変更を加えていく。
ListBox は子要素として ListBoxItem を持っているが、既定では線がないので境界が見えない。そこに境界をつけるということである。 さっそくつけていく。各 Item の Template に対しての DataTemplate を変更する。
Border を使うことにより、境界線を実現する。 xaml 側の変更を実現するため、C# 側にも変更を加えている。
「itemN」をプロパティに格納し、それをバインディングする形にしないと Text に対してのバインディングソースがわからないためである。 これを実行すると下記のようになる。
ControlTemplate のように外観を変更することができた。今回は境界線をつけるだけの簡単なものだったが、さらに Grid を使って行と列を定義して・・・と外観を設定することもできるため、DataTemplate の可能性は結構広いことになる。 ItemsPanelTemplate と DataTemplate をまとめた。さらに概略だけをまとめたものを下記に載せる。Template の種類
種類
説明
DataTemplate
必ずしも外観を持たなくてよいデータオブジェクトに対して外観を持たせたいときに使用する Template 。例えば、グラデーションオブジェクト。グラデーションは色なので外観がない。そこで DataTemplate によって例えば楕円という外形を与え、その色としてグラデーションオブジェクトを使うといったことができる。
ItemsPanelTemplate
ItemsControl の派生クラスで各要素をどんなパネルに表示するか変えたいときに使用する。この Template に指定できるのは Panel の派生クラスのみ。例えば、StackPanel である。
ControlTemplate
標準コントロールの外観を再定義するための Template 。超重要な Template 。その2にまとめた。
HierarchicalDataTemplate
階層構造を持ったものの外観を定義するための Template 。実問題におけるデータ構造は階層構造を持つことが多いと考えられるため、これは習得しておきたい。
ItemsPanelTemplate
<ListBox HorizontalAlignment="Center"
ItemsSource="{Binding Source={StaticResource stringItems}, Path=ItemList}">
</ListBox>
class StringItemsForListBox
{
public static IEnumerable<string> ItemList { get; private set; }
static StringItemsForListBox()
{
var list = new List<int>();
for (int i = 0; i < 100; i++)
{
list.Add(i);
}
ItemList = list.Where(x => x % 2 == 0).Select(x => $"item{x}");
}
<ListBox HorizontalAlignment="Center"
ItemsSource="{Binding Source={StaticResource stringItems}, Path=ItemList}">
<!--#region PanelTemplateの変更 -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--#endregion-->
</ListBox>
DataTemplate
<Button HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- このグラデーションがバインディングソース -->
<LinearGradientBrush>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Button>
<Button HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- このグラデーションがバインディングソース -->
<LinearGradientBrush>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
<!--#region DataTemplateを更新 -->
<Button.ContentTemplate>
<DataTemplate>
<Ellipse Width="100" Height="100"
Fill="{Binding}"/>
</DataTemplate>
</Button.ContentTemplate>
<!--#endregion-->
</Button>
DataTemplate で既存の外観を変更する
<ListBox HorizontalAlignment="Center"
ItemsSource="{Binding Source={StaticResource stringItems}, Path=ItemList}"
BorderBrush="Red">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Gray"
BorderThickness="1">
<TextBlock Text="{Binding Content}" Margin="5"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<!--#region PanelTemplateの変更 -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--#endregion-->
</ListBox>
class StringItemsForListBox
{
public static IEnumerable<StringItemsForListBox> ItemList { get; private set; }
public string Content { get; private set; }
static StringItemsForListBox()
{
var list = new List<int>();
for (int i = 0; i < 100; i++)
{
list.Add(i);
}
ItemList = list.Where(x => x % 2 == 0).Select(x => new StringItemsForListBox
{
Content = $"item{x}"
});
}
}
まとめ
Template
まとめ
ItemsPanelTemplate
・ItemsControl の表示の外観を簡単に変更できる。
・ 指定できるのは Panel の派生クラスのみ。
・ Panel の派生クラスなら自作クラスでもよいため、自由な変更も可能である。
DataTemplate
・必ずしも外観が必要とならないものに外観を与えることができる。
・ControlTemplate のように既存の外観を変更することもできる。