Last Updated 2012/12/05
System.Windows.Forms.TreeView クラスと System.Windows.Controls.TreeView クラスとは、まさに似て非なるものです。このページでは、WPF 対応の System.Windows.Controls.TreeView クラスについて解説します。
TreeView クラスの使い方について調べようと WPF SDK や WEB 上で公開されている情報をあたると、情報自体はたくさんあるのですが、サンプルのためのサンプルといったおもむきのものが多くて実用性を感じないことが多いものです。そこで、ここではほかのサイトでは扱わないようなテーマについて取り上げます。
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 はその逆ですね。
GetPrevVisibleItem と GetNextVisibleItem の "Visible" は見えているという意味です。指定の項目のすぐ上にある項目が子項目を持つが閉じているときは子項目ではなく上の項目を返します。開いているときは見えている項目のうちの一番下にある項目を返します。階層のズレは複数段でも可能です。
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 コントロールは階層化したデータを表示するのに適していますが、データの構造があらかじめ分かっているような場合はツリーの構築も簡単です。
TreeView コントロールに関係する公開されている多くのサンプルコードはデータ構造が既知であることを前提にするものが多いですね。しかし、実務的にはこういうケースは少ないのではないかと思います。
しかし、問題はデータの構造が既知でない場合はどうすればいいかです。その代表的な例がシステム(OS としての Windows)のフォルダを表示する場合です。そこで、TreeView コントロールを利用する例として、フォルダ選択ダイアログボックスを考えてみました。
WEB サイトなどで公開されているフォルダ選択ダイアログボックスは Windows API 関数を直接呼び出すことで構築していますが、ここで紹介するコントロールは基本的に .NET Framework/WPF で作っています。ただし、システムアイコンを取得する機能がありませんので、その部分だけは API 関数を使いました。
フォルダ選択ダイアログボックスとしては System.Windows.Forms.FolderBrowserDialog クラスがありますから、WPF からでも利用することができます。しかし、フォーム(WPF のウインドウ)に貼り付けて使うフォルダ選択コントロールはありません。そこで、作ることにしました。下図のピンク色の部分がフォルダ選択コントロールで、その下に選択したディレクトリ名を表示するテキストボックス、右に選択したフォルダ内にあるファイルをリストアップするリストボックスを配置しています。
このコントロールの説明とソースコードはここに収録しておきました。なお、このクラスは UserControl として作りましたからダイアログボックスのような使い方をしたい場合はフォームに配置してください。
現在の私の環境は Windows 7 + .NET Framework 4 ですが、この環境における TreeView コントロールの動作に不満があります。コントロールの幅より項目のテキストの幅が広いとき、コントロールは項目をできるだけ表示しようとするからでしょうか、勝手に水平スクロールしてしまいます。この動作は、私には親切の押し付けとしか思えません。
ついでに言うと、ノードをあらわす下向きの三角形も好きになれません。ひどく視認性が悪いからです。Windows XP スタイルのほうが好きですね。
この水平スクロールを抑制する手順は TreeViewItem クラスの RequestIntoView イベント内で e.Handled = true にすることです。
<TreeView> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/> </Style> </TreeView.ItemContainerStyle> </TreeView>
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(); }
−以上−