TreeView コントロール

Last Updated 2012/12/05


System.Windows.Forms.TreeView クラスと System.Windows.Controls.TreeView クラスとは、まさに似て非なるものです。このページでは、WPF 対応の System.Windows.Controls.TreeView クラスについて解説します。


TreeView クラスの使い方について調べようと WPF SDK や WEB 上で公開されている情報をあたると、情報自体はたくさんあるのですが、サンプルのためのサンプルといったおもむきのものが多くて実用性を感じないことが多いものです。そこで、ここではほかのサイトでは扱わないようなテーマについて取り上げます。


TreeViewHelper クラス

Windows フォームアプリケーション用の System.Windows.Forms.TreeView コントロールは基本的に Windows コモンコントロールをラップしたものですからそれに対応する形でたくさんのプロパティやメソッドが用意されています。しかし、WPF アプリケーション用の System.Windows.Controls.TreeView コントロールにはあれば便利だろうと思う機能はほとんどありません。

階層化したデータを単に表示するだけなら現状のままでも差し支えないのですが、データを操作したい場合には機能の少なさに絶望的な思いになります。しかし、だからといって、不可能というわけではありません。

"TreeViewHelper" をキーワードにして Google をあたると多くのサイトにヒットします。ソースコードが公開されているものがたくさんありますので、見てみました。しかし、残念ですね。もともとヘルパークラスを作る必要がないだろうと思うものをクラス化していたり、実務的に使えるのだろうかと疑問に感じるものもあります。

私は .NET Framework の前身である Delphi/ C++ Builder 対応の VCL "Visual Component Library" を使っていたことがありますので、その便利さを知っています。そこで、それらを参考にして TreeView コントロールを操作するためのヘルパークラスを作ってみました。

まず、TreeViewHelper クラスの機能を以下にリストアップします。

LoadFromFile階層化データをファイルから読み込む
LoadFromXmlFile階層化データを定義する XML ファイルから読み込む
LoadFromStream階層化データを保持するストリームから読み込む
LoadFromStringArray階層化データを保持する文字列配列から読み込む
LoadFromStringArray階層化データを保持する文字列配列から読み込み、Tag プロパティに指定のオブジェクトを設定する
InsertBefore指定の項目の前に新しい項目を追加する
InsertAfter指定の項目のうしろに新しい項目を追加する
Remove指定の項目を削除する
ExpandAllすべてのノードを展開する
CollapseAllすべてのノードを閉じる
GetFirstItem同じレベル内の先頭の項目を取得する
GetLastItem同、最後の項目を取得する
GetNextItem同一レベル内における次の項目を取得する
GetPrevItem同、次の項目を取得する
GetIndexFromItem指定の項目の同一レベル内におけるインデックスを取得する
MoveLeft指定の項目を左に移動する
MoveRight同、右に移動する
MoveUp同、上に移動する
MoveDown同、下に移動する
GetPrevVisibleItem指定の項目から検索を開始し、表示状態にある直前の項目を取得する
GetNextVisibleItem指定の項目から検索を開始し、表示状態にある次の項目を取得する
GetAllItemsすべてのノードのリストを取得する
GetLevel指定の項目のレベル(深度)を取得する
GetParentItem指定の項目の親項目を取得する
GetItemFromObject指定のオブジェクトから TreeViewItem オブジェクトを取得する
GetItemContainer指定のオブジェクトのコンテナオブジェクトを取得する

いくつかの項目について補足しておきます。

LoadFromFile メソッドのデータファイルのデータの形式は階層を [Tab] 文字で表現します。たとえば、こんな感じです。

  北海道
  →札幌市
  →→中央区
  →→→大通西
  →→→大通東
  →旭川市
  →→東旭川町倉沼

  → は [Tab] です。

なお、ファイル読み込み時の文字のエンコーディング法を指定しなければなりません。もし必要なら、このサイト内で文字のエンコーディング法を判定する clsTextEncoding クラスを公開していますので、参考にしてください。

LoadFromXmlFile メソッドは XML ファイルからデータを読み込んで TreeView コントロールのツリー構造を構築しますが、XML データを使って構築した TreeView クラスの SelectedItem プロパティの戻り値は、XmlElement オブジェクトです。したがって、ヘッダーを取得するにはチョットした手続きが必要です。

var item = treeView.SelectedItem as System.Xml.XmlElement;
var attr = item.Attributes;

Title = attr[0].InnerText;

MoveLeft は項目を左、つまり、一つ上の階層に移動します。MoveRight はその逆ですね。

GetPrevVisibleItemGetNextVisibleItem の "Visible" は見えているという意味です。指定の項目のすぐ上にある項目が子項目を持つが閉じているときは子項目ではなく上の項目を返します。開いているときは見えている項目のうちの一番下にある項目を返します。階層のズレは複数段でも可能です。

Note TreeView コントロールにフォーカスがあるとき、上下矢印キー([↓] または [↑])を操作するとフォーカスは見えている項目上を移動する機能をもともと持っています。しかし、私は TreeView にフォーカスがないときにコードを使って項目のフォーカスを移動させる機能がほしかったので、これらのメソッドを作りました。

GetAllItems はすべての項目を IList<TreeItemData> オブジェクトに格納して返します。TreeItemData クラスはTreeViewItem クラスからから派生したクラスで、項目のレベルをデータとして持ちます。

GetLevel は指定の項目のレベル(深度)を返します。トップレベルは 0 です。

以下は、TreeViewHelper をテストするアプリケーションのソースファイルです。Visual Studio 2010 C# / WPF で作りました。フリーウエアです。ソースファイルの改変・流用も自由です。

TreeViewHelperTest.zip (54,108 bytes)


マウスによる項目の移動

TreeViewHelper クラスはキーボード操作による項目の移動をサポートしますが、大きく移動する場合はマウスを使うほうが便利です。これを実現する機能をクラス化はしたわけではありませんが、テスト用のアプリケーションとして作成しました。

また、このアプリケーションは TreeView コントロールの上端あるいは下端にマウスが到達したとき、自動的にスクロールする機能を持ちます。ちなみに、この部分はクラス化しておきましたので、再利用性が高いと思います。なお、移動した項目を挿入する位置はドロップ位置の項目のすぐ上になります。

以下は、マウスで項目を移動するテスト用アプリケーションです。Visual Studio 2010 C# / WPF で作りました。フリーウエアです。ソースファイルの改変・流用も自由です。

TreeItemMoveTest.zip (29,896 bytes)


フォルダ選択ダイアログボックス

TreeView コントロールは階層化したデータを表示するのに適していますが、データの構造があらかじめ分かっているような場合はツリーの構築も簡単です。

Note TreeView コントロールに関係する公開されている多くのサンプルコードはデータ構造が既知であることを前提にするものが多いですね。しかし、実務的にはこういうケースは少ないのではないかと思います。

しかし、問題はデータの構造が既知でない場合はどうすればいいかです。その代表的な例がシステム(OS としての Windows)のフォルダを表示する場合です。そこで、TreeView コントロールを利用する例として、フォルダ選択ダイアログボックスを考えてみました。

Note WEB サイトなどで公開されているフォルダ選択ダイアログボックスは Windows API 関数を直接呼び出すことで構築していますが、ここで紹介するコントロールは基本的に .NET Framework/WPF で作っています。ただし、システムアイコンを取得する機能がありませんので、その部分だけは API 関数を使いました。

フォルダ選択ダイアログボックスとしては System.Windows.Forms.FolderBrowserDialog クラスがありますから、WPF からでも利用することができます。しかし、フォーム(WPF のウインドウ)に貼り付けて使うフォルダ選択コントロールはありません。そこで、作ることにしました。下図のピンク色の部分がフォルダ選択コントロールで、その下に選択したディレクトリ名を表示するテキストボックス、右に選択したフォルダ内にあるファイルをリストアップするリストボックスを配置しています。

FolderBrowserControl

このコントロールの説明とソースコードはここに収録しておきました。なお、このクラスは UserControl として作りましたからダイアログボックスのような使い方をしたい場合はフォームに配置してください。


水平スクロールを抑制する

現在の私の環境は Windows 7 + .NET Framework 4 ですが、この環境における TreeView コントロールの動作に不満があります。コントロールの幅より項目のテキストの幅が広いとき、コントロールは項目をできるだけ表示しようとするからでしょうか、勝手に水平スクロールしてしまいます。この動作は、私には親切の押し付けとしか思えません。

Note ついでに言うと、ノードをあらわす下向きの三角形も好きになれません。ひどく視認性が悪いからです。Windows XP スタイルのほうが好きですね。

この水平スクロールを抑制する手順は TreeViewItem クラスの RequestIntoView イベント内で e.Handled = true にすることです。

XAML コード

<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>

C# コード

private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
  e.Handled = true;
}

指定のリスト項目を表示位置までスクロールする

ListBox クラスには ScrollIntoView メソッドがあって、指定のリスト項目を表示位置までスクロールすることができます。しかし、TreeView クラスにこのような機能はありません。

こういうところが WPF のややこしいところですが、これを実現する機能は意外なところに存在します。つまり、TreeViewItem クラスは FrameworkElement クラスを継承しますが、この中の BringIntoView メソッドを使います。

private void treeViewItem_Expanded(object sender, RoutedEventArgs e)
{
  var item = e.Source as TreeViewItem;

  if (item != null)
    item.BringIntoView();
}


−以上−