前回:http://blogs.wankuma.com/kazuki/archive/2008/02/03/120702.aspx
前回までと毛色を変えて、今回はHierarchicalDataTemplateに手を出してみようと思う。
HierarchicalDataTemplateっていうのは、DataTemplateを継承して作られたものです。
DataTemplateとHierarchicalDataTemplateの関係をイメージしやすいようにコードにしてみた。
public class DataTemplate
{
public FrameworkElementFactory VisualTree { get; set; }
public Type DataType { get; set; }
}
public class HierarchicalDataTemplate : DataTemplate
{
public DataTemplate ItemTemplate { get; set; }
public DataTemplateSelector ItemTemplateSelector { get; set; }
public BindingBase ItemsSource { get; set; }
}
何だかHierarchicalDataTemplateのほうは、ListBoxとかで見たような名前のプロパティが追加されてるのがわかる。
そもそも、HierarchicalDataTemplateっていうのは何かと言うと、TreeViewみたいな階層構造を持ったものにバインドするためのDataTemplateです。
階層構造を持ったデータということで、Personクラスを拡張してみようと思います。
とりあえず、Childrenプロパティで階層構造をあらわせるようにしてみる。
using System.Collections.Generic;
namespace WpfTemplate
{
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public IList<Person> Children { get; set; }
}
}
例によってWindowのDataContextに表示のための元データを突っ込む。
using System.Linq;
using System.Windows;
using System.Collections.Generic;
namespace WpfTemplate
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var p = new Person { Name = "太郎", Age = 20 };
p.Children = new List<Person>
{
new Person { Name = "花子", Age = 12 },
new Person { Name = "梅子", Age = 11 }
};
this.DataContext = Enumerable.Repeat(p, 1);
}
}
}
そして、Window1.xamlに定義してあるDataTemplateをHierarchicalDataTemplateに書き換える。
ItemsSourceプロパティにChildrenプロパティをバインドしておく。
<HierarchicalDataTemplate x:Key="personViewTemplate" DataType="{x:Type WpfTemplate:Person}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="さん " />
<TextBlock Text="{Binding Age}"/>
<TextBlock Text="歳" />
</StackPanel>
</HierarchicalDataTemplate>
最後に、ListBoxをTreeViewに置き換えて完成。ContentControlは、とりあえずダミー文字列を出すようにしておいた。
<Window x:Class="WpfTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTemplate="clr-namespace:WpfTemplate"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate x:Key="personViewTemplate" DataType="{x:Type WpfTemplate:Person}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="さん " />
<TextBlock Text="{Binding Age}"/>
<TextBlock Text="歳" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Name="personTree"
Grid.Row="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource personViewTemplate}"/>
<ContentControl
Grid.Row="1"
Content="まだ無い" />
</Grid>
</Window>
これを実行すると、下のような結果になる。
ちゃんと、Treeに表示されてる。
TreeViewには、ListBoxみたいなIsSynchronizedWithCurrentItemというプロパティが無いので、下の部分のContentControlに選択中のデータを表示するためにはちょびっと工夫が必要になる。
TreeViewに名前をつけて、TreeViewのSelectedItemプロパティとContentControlのContentプロパティをバインドする。
<Window x:Class="WpfTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTemplate="clr-namespace:WpfTemplate"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate x:Key="personViewTemplate" DataType="{x:Type WpfTemplate:Person}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="さん " />
<TextBlock Text="{Binding Age}"/>
<TextBlock Text="歳" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Name="personTree"
Grid.Row="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource personViewTemplate}"/>
<ContentControl
Grid.Row="1"
Content="{Binding ElementName=personTree, Path=SelectedItem}"
ContentTemplate="{StaticResource personViewTemplate}"/>
</Grid>
</Window>
これを実行すると、ちゃんと選択してるものが表示される。
ばっちりだ!
最後に、Tree状のものを表示するときに気になる大量のTree状のデータを表示するのにすんごい時間がかかるんだけど…というありがちな問題について試してみる。
WindowsFormsあたりだと、展開されてないものは、子要素があるか無いかだけチェックしておいて、実際の要素の取得は、Treeが展開されたときに行うというのが常套手段として用いられてる。
TreeView + HierarchicalDataTemplateはどうだろうかということで、WindowのDataContextに突っ込むデータを下のような感じにしてみた。
using System.Linq;
using System.Windows;
using System.Collections.Generic;
namespace WpfTemplate
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var p = new Person { Name = "太郎", Age = 20 };
p.Children = new List<Person>
{
new Person { Name = "花子", Age = 12 },
new Person { Name = "梅子", Age = 11 },
p // 自分自身を子にもつ!無限再帰だ
};
this.DataContext = Enumerable.Repeat(p, 1);
}
}
}
これで実行して、TreeViewを表示するのに一生懸命で応答が無かったらTreeView + HierarchicalDataTemplateは大量データの表示に使えないことになりそう。(もしくは一工夫がいるかも)
ということで実行!
全然大丈夫だった。
ということでいい感じ。