TreeViewの作り方の話(UWP/C#)

 UWPのTreeViewの作り方には2種類あります。どちらも見た感じは変わりませんが、そのツリーを操作しようとした時のやり方が結構ちがってきます。

 ソース例も含め、詳しくはコチラにすべて載っています。
公式:https://docs.microsoft.com/ja-jp/windows/uwp/design/controls-and-patterns/tree-view

パターン1:データバインディングを使う方法

  まずデータクラスTを好きに作ります。この時、Tのプロパティには、必ず「ObservableCollection<T> Children」を持たせるようにします。このChildrenに自身と同じデータを入れていくことで階層構造のデータを作ります。またTが持つプロパティについては必要に応じてINotifyPropertyChangedを実装しましょう(バインディングの記事参照)。

  次にメインクラスでObservableCollection<T>を生成します。仮にこのコレクションの名前をDataSourceとします。

 xaml側では、TreeViewのItemsSourceのバインド先としてDataSourceを指定し、そのノードを表すTreeViewItemのItemsSourceとしてChildren(Tに自分で持たせたプロパティです)を指定します。

<!--xamlの記述例-->
<TreeView ItemsSource="{x:Bind DataSource}">
    <TreeView.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <TreeViewItem ItemsSource="{x:Bind Children}"
                               Content="{x:Bind Hoge}"/>
        </DataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

 上記例で「Content」がHogeにバインドされています。Contentはノードに表示する文字列のことです。これもTに自分でプロパティを用意することになります。また、このContentをバインドさせる記述を省略した場合、デフォルトでTのToString()が表示されるようになってますので、ToString()をオーバーライドするのもアリでしょう。

 特徴は次のような感じです。

  • データを変えればツリーが変わるので便利。
  • InvokedItemイベントで取得されるのはTreeViewItem。

 一見すると何も問題はなさそうなのですが、公式に記載のドラッグアンドドロップによる並び替え(ツリー内)をそのままマネすると、見た目は変わってもデータソースは変わらないという現象が発生しました。
 TreeView.RootNodes内のTreeViewNodeの並びは変わるけど、TreeView.ItemsSourceおよびTreeViewNode.Content.Childrenはそのまんまという状態です。
 バインドの仕方が間違っているのか、ドラッグアンドドロップイベント内で手動でデータリストを入れ替えなければいけないのか、そういう問題だと思いますが、私の手には余りましたのであきらめました。

パターン2:データバインディングを使わず手動でツリーを作る方法

 クラス内でTreeViewNodeを生成して、TreeView.RootNodesに突っ込む方法です。データはnode.Contentに個別に設定していきます。

 特徴は次のような感じです。

  • InvokedItemイベントで取得されるのはTreeViewNode。
  • データのアクセスはTreeViewNodeを介するのでキャストが必須。たとえば((T)TreeViewNode.Content).hoge = X;

 ItemsSourceを使いませんので、前述のドラッグアンドドロップ問題が発生しません。

 TreeViewNodeを階層追加していく作業を手間に感じるかもしれませんが、データバインディングパターンの場合も結局、ObservableCollectionにデータを階層追加していく必要はあるので、実際の作業量はあんまり変わらない印象です。

ノードの状態をプログラムで制御する

 ツリービューにおけるノードの状態は TreeViewItemが保持します。

 例えばプログラムから「このノードを選択状態にする」といった制御をしたい場合、TreeViewItemのIsSelectedプロパティを設定します。

 前者のバインディングパターンでは問題ないですが、後者のバインディングを使わないパターンの場合、TreeViewItemにアクセスするには、いったんTreeViewNodeを取得し、ContainerFromNode(TreeViewNode)で該当のTreeViewItemを取得する必要があります。このメソッドはUI構築前にアクセスするとエラーが発生しますので、タイミングをうまくコントロールしてください。またツリーの更新状況によっては取れるはずのTreeViewItemが取れないこともありますので、nullチェックを入れておいた方が安全です。

溟犬一六(Ichiro Meiken)
  • 溟犬一六(Ichiro Meiken)
  • フリーランスのWEBクリエイター。小説、ゲーム、アプリなど幅広く活動中。
    Twitter : @dawn_gabacho