久しぶりの WPF ネタです。WPF から始まった MS 系の XAML で画面を定義するテクノロジーの中で、後発のプラットフォームでは採用されていない機能がいくつかあります。
MultiBinding とか、今回紹介する HierarchicalDataTemplate がそれにあたります。今回は HierarchicalDataTemplate に注目していきます。
前提
WPF では、データを画面に表示するときに、どのように表示するかという定義を DataTemplate というものを使って書きます。例えば以下のような Person クラスがあったとします。
namespace Xxx
{
public class Person
{
public string Name { get; set; }
}
}
これを画面に文字列として Name を表示したいと思ったら以下のような感じのテンプレートになります。
<!-- 何処かで xmlns:xxx="clr-namespace:Xxx" を定義している前提 -->
<DataTemplate TargetType="xxx:Person">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
DataTemplate 対応コントロールには ContentTemplate や ItemTemplate といったプロパティがあって、ここに上記の DataTemplate の定義を設定することで見た目を指定できます。
HierarchicalDataTemplate
HierarchicalDataTemplate は、階層構造を持つようなデータの見た目を定義するためのテンプレートになります。例えば以下のような Person クラスがあったとします。
using System.Collections.ObjectModel;
namespace WpfApp40
{
public class Person
{
public string Name { get; set; }
public ObservableCollection<Person> Children { get; } = new ObservableCollection<Person>();
}
}
先ほどの例と比較すると Children プロパティが増えていますね!!Name を表示したあとに子要素として Children の Name も列挙したいというようなケースを考えてみましょう。階層構造のデータの表示に対応したコントロールとしては TreeView や ContextMenu コントロールがあります。今回は諸事情により ContextMenu でやってみようと思います。
MainPage.xaml.cs の DataContext に先ほどのクラスの配列を入れておきます。
using System.Windows;
namespace WpfApp40
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new Person
{
Name = "Taro1",
Children =
{
new Person
{
Name = "Taro1-1",
Children =
{
new Person { Name = "Taro1-1-1" },
new Person { Name = "Taro1-1-2" },
new Person { Name = "Taro1-1-3" },
new Person { Name = "Taro1-1-4" },
}
},
new Person { Name = "Taro1-2" },
new Person { Name = "Taro1-3" },
},
},
new Person
{
Name = "Taro2",
Children =
{
new Person { Name = "Taro2-1" },
new Person { Name = "Taro2-2" },
new Person { Name = "Taro2-3" },
},
},
};
}
}
}
そして、画面にボタンを置いて、そのボタンのコンテキストメニューに HierarchicalDataTemplate を指定します。ポイントは DataTemplate の時と同様に Name プロパティを TextBlock で表示しているのに加えて ItemsSource プロパティで子要素として Children プロパティを指定しているところです。こうすることで、子要素には Children の中の Person クラスが列挙されて、その見た目が再度 HierarchicalDataTemplate で表示されるといった形になります。
<Window x:Class="WpfApp40.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp40"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="XXXX" MinWidth="250">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
</Window>
実行すると以下のようになります。
これに DataTemplateSelector とかを組み合わせると、割と自由自在に階層構造を持ったデータを表現できて捗ります。
まとめ
WPF 強い。