今回はTreeViewコントロールの使い方です。

まずデザイナでForm1にTreeViewコントロールとボタンを貼り付けます。

そのあとTreeViewコントロールのプロパティを設定します。これは必須ではないけれどもこのように設定したほうがわかりやすいかもしれません。treeView1.HideSelection = false;と設定しておかないとTreeViewコントロール以外の部分をクリックしてしまった場合、どの項目が選択されているのかがわかりにくくなります。

TreeViewコントロールへの項目の追加

項目の追加ですが、

選択されてるアイテムの一番上の子として追加
選択されてるアイテムの一番下の子として追加
選択されてるアイテムのひとつ上に追加
選択されてるアイテムのひとつ下に追加

の4つのなかから選べるようにします。ただ最初はTreeViewコントロールのなかに項目はひとつもないので、その場合は自作したCreateRootNodeメソッドで最初の項目を追加して、それが選択されている状態にします。

項目の名前は「新しいノード (番号)」にして何番目に追加されたものかがわかるようにしています。

選択されてるアイテムの一番上の子として追加

新しい項目はTreeNodeのコンストラクタを呼び出して生成します。TreeNodeのコンストラクタは複数ありますが、アイコンをつかわず名前だけでよいのであれば new TreeNode(“項目の名前”)がよいと思います。

選択されてるアイテムの一番上の子として追加するときは、選択されているTreeNodeがあるか調べます。ない場合は上記のメソッドで最初の項目を追加します。選択されているTreeNodeが存在する場合はそのNodesの一番最初に新しく生成したTreeNodeのインスタンスを挿入します。

新しいTreeNodeを挿入したら、それが選択されている状態にします。そしてnodeNumberをインクリメントして次に追加する項目の名前を生成できるようにしておきます。

選択されてるアイテムの一番下の子として追加

選択されてるアイテムの一番下の子として追加するときも、ほぼ同じです。選択されているTreeNodeが存在する場合はそのNodesの一番最後に新しく生成されたTreeNodeのインスタンスを追加します。

選択されてるアイテムのひとつ上に追加

選択されてるアイテムのひとつ上に追加するときは選択されているTreeNodeがあるか調べます。ない場合はCreateRootNodeメソッドで最初の項目を追加します。

選択されているTreeNodeが存在する場合はさらにその親にあたるTreeNodeがあるかを調べます。ある場合は選択されているTreeNodeが親の上から何番目の子なのかを調べます。これはIndexOfメソッドを使えばわかります。

新しく生成したTreeNodeを親のNodesにInsertメソッドをつかって挿入します。このときの第一引数はIndexOfメソッドが返した値をそのまま使います。

選択されているTreeNodeの親が存在しない場合はTreeView.Nodesに対してInsertメソッドをつかって挿入します。このときの第一引数の求め方は親が存在する場合と同じです。

選択されてるアイテムのひとつ下に追加

選択されてるアイテムのひとつ下に追加するときも選択されてるアイテムのひとつ上に追加するときと基本は同じです。違いは親のNodesまたはTreeView.Nodesに新しいTreeNodeを挿入するためのInsertメソッドの引数です。IndexOfメソッドが返した値に1加えた数を使わなければなりません。

削除するとき

選択されている項目を削除するのは簡単です。選択されているTreeNodeのRemoveメソッドを呼び出せば削除されます。この場合、選択されている項目だけでなくその子孫全体が削除されてしまいます。

選択されている項目を移動させる

選択されている項目を移動させる場合、その多くはドラッグアンドドロップでおこないます。

最初にドラッグアンドドロップに対応させる処理が必要です。TreeView.AllowDrop = trueにすると同時にドラッグが開始されたとき、ドラッグされているとき、ドロップされたときのイベントハンドラを追加します。

TreeView.ItemDragイベントはドラッグアンドドロップが開始された時に呼び出され、ドロップが完了したときに終了します。ItemDragEventArgs.Itemを調べればどのTreeNodeがドラッグされはじめたのかがわかります。

移動させる場合、移動先が移動元の子孫であると困ります。そこで本当に移動できるのかを調べるメソッドを作成しています。移動元と移動先のどちらかがnullであったり、移動先の親をたどっていくとそこに移動元のTreeNodeがある場合は移動できないのでfalseを返しています。最上位のTreeNodeにたどり着き、親は存在しないところまでたどり着いた場合は、移動先が移動元の子孫ではないので移動可能ということになります。

ドラッグされているときの処理を示します。ドロップを受け入れるのはTreeNodeだけです。DragEventArgs.Data.GetDataPresent(typeof(TreeNode))がtrueならTreeNodeがドラッグされていることになります。さらに TreeNode dragSource = (TreeNode)DragEventArgs.Data.GetData(typeof(TreeNode))とすればどのTreeNodeがドラッグされているかがわかります。

TreeNodeがドラッグされているときでも移動可能でない場合はドロップは受け入れません。移動可能であるときはDragDropEffects.Moveとし、それ以外の時はe.Effect = DragDropEffects.Noneとします。

またドロップ可能なTreeNodeのうえにマウスポインタが存在する場合はそのTreeNodeを選択状態にします。移動できない場合はドラッグされているTreeNodeを選択します。

マウスポインタがどのTreeNodeのうえに存在するかはどうやって調べればよいのでしょうか? マウスポインタがどこにあるかはDragEventArgs.XとDragEventArgs.Yを調べればわかります。ただしこの座標はスクリーン座標であり、TreeViewコントロール上のクライアント座標ではありません。そこでTreeView.PointToClientメソッドでクライアント座標で変換します。あとはTreeView.HitTestメソッドを呼び出せばわかります。

TreeView.HitTestメソッドはTreeViewHitTestInfoを返します。これを調べるとこの座標に存在するのはどのTreeNodeかだけでなくTreeViewHitTestInfo.Locationがどうなっているかで、TreeNodeのどの部分なのか(Label部分かアイコンを使用している場合はアイコン部分か?など)もわかります。

ドロップされたときの処理を示します。

ドラッグされているTreeNodeとドロップのターゲットになるTreeNodeを取得する部分は同じです。あとはドラッグされているTreeNodeをTreeViewからいったん取り除き、これをドロップのターゲットになるTreeNodeの一番下の子として追加します。そして移動先のTreeNodeを選択状態にします。