WPF にしかない便利機能、階層を持ったデータを表示する HierarchicalDataTemplate

久しぶりの WPF ネタです。WPF から始まった MS 系の XAML で画面を定義するテクノロジーの中で、後発のプラットフォームでは採用されていない機能がいくつかあります。

MultiBinding とか、今回紹介する HierarchicalDataTemplate がそれにあたります。今回は HierarchicalDataTemplate に注目していきます。

## 前提

WPF では、データを画面に表示するときに、どのように表示するかという定義を DataTemplate というものを使って書きます。例えば以下のような Person クラスがあったとします。

```csharp
namespace Xxx
{
  public class Person
  {
    public string Name { get; set; }
  }
}
```

これを画面に文字列として Name を表示したいと思ったら以下のような感じのテンプレートになります。

```xml
<!-- 何処かで xmlns:xxx="clr-namespace:Xxx" を定義している前提 -->
<DataTemplate TargetType="xxx:Person">
  <TextBlock Text="{Binding Name}" />
</DataTemplate>
```

DataTemplate 対応コントロールには ContentTemplate や ItemTemplate といったプロパティがあって、ここに上記の DataTemplate の定義を設定することで見た目を指定できます。

## HierarchicalDataTemplate

HierarchicalDataTemplate は、階層構造を持つようなデータの見た目を定義するためのテンプレートになります。例えば以下のような Person クラスがあったとします。

```csharp
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 に先ほどのクラスの配列を入れておきます。

```csharp
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 で表示されるといった形になります。

```xml
<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>
```

実行すると以下のようになります。

![contextmenu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/57bc2b92-4ec5-d1fe-d037-91d72cb3d23a.gif)

これに DataTemplateSelector とかを組み合わせると、割と自由自在に階層構造を持ったデータを表現できて捗ります。

## まとめ

WPF 強い。