.NET TIPS

TreeViewコントロールで効率的にツリーを構築するには?

デジタルアドバンテージ 一色 政彦
2005/02/04

 「TIPS:TreeViewコントロールへ項目を追加するには?」では、TreeViewコントロールでツリーを構築するために、複数のツリーのデータ項目(ノード)をまとめて登録する方法や1つずつ追加する方法を紹介した。

 特に複数のノードをまとめて追加するツリー構築手法は、そのツリー形状が固定的な場合には最も効率がよい。しかし例えばレジストリ・エディタやエクスプローラのように、ツリー階層が非常に深く、またその深さが不定の場合、ツリー全体の項目を最初にすべて追加しようとすると、その処理に時間がかかってしまい、アプリケーションのパフォーマンスが低下する可能性がある。

 そこで、本稿では適切なタイミングで必要なだけのツリー・ノードを追加する方法を紹介する。これにより、ツリー全体の構築が効率的になる。

適切なタイミングで必要なだけのツリー・ノードを追加する方法

 TreeViewコントロールに最も効率的にノードを登録できるタイミングは、TreeViewコントロールのBeforeExpandイベントが発行されたときである。このBeforeExpandイベントは、ノードが展開される直前に発行される。

 このイベントのタイミングで展開されようとしているノードに子ノードを追加すればよいのだが、それだけではそれぞれの子ノードが展開可能かどうか(子ノードを持っているかどうか)を示すことができない(ノードに子ノードがあり展開可能な場合には、それを示す「+」マークがノードの左横に表示される。ただし、TreeViewコントロールのShowPlusMinusプロパティがTrueの場合のみ。既定ではTrue)。このため、あるノードが展開されるときには、それより2段階下のノード(以降、孫ノード)まで追加しておかなければないということになる。

 言葉の説明だけではなかなかイメージしづらいので、具体的な例を使って説明することにしよう。

 ここでは、ツリー全体が次のような構造であるとする。

 例えば上記のツリーの「食べ物」のノードを展開しようとしている場合、「果物」と「野菜」の子ノードを追加しているだけでは、「果物」の左横に展開可能な「+」マークが表示されない。このため「食べ物」のノードが展開される直前には、「果物」に対して「りんご」と「みかん」(「食べ物」から見ると、孫ノード)が追加されていなければならないのである。

 ただし、BeforeExpandイベントはノードが展開されるたびに発行されるので、同じノードが何度も登録されないようにするために、何らかの手段で子ノードがすでに追加済みかどうかを管理する必要がある。

●子ノードの管理機能を持つ独自のTreeNodeExクラス

 そこで本稿では、ノードを表すTreeNodeクラス(System.Windows.Forms名前空間)を継承して、そのような管理機能をノード自身に持たせることにする。このようにして作成したTreeNodeExクラスを以下に示す。

public class TreeNodeEx : System.Windows.Forms.TreeNode
{
public TreeNodeEx(string text) :
    base(text)
  {
    InitializeComponent();
  }

  private void InitializeComponent()
  {
    isSubFoldersAdded = false;
  }

  private bool isSubFoldersAdded;
  public bool SubFoldersAdded
  {
    get
    {
      return isSubFoldersAdded;
    }
    set
    {
      isSubFoldersAdded = value;
    }
  }
}
子ノードが追加済みかどうかを管理する機能を持つTreeNodeExクラス(C#)
 
Public Class TreeNodeEx
  Inherits System.Windows.Forms.TreeNode

  Public Sub New(ByVal [text] As String)
    MyBase.New([text])
    InitializeComponent()
  End Sub

  Private Sub InitializeComponent()
    isSubFoldersAdded = False
  End Sub

  Private isSubFoldersAdded As Boolean
  Public Property SubFoldersAdded() As Boolean
    Get
      Return isSubFoldersAdded
    End Get
    Set(ByVal Value As Boolean)
      isSubFoldersAdded = Value
    End Set
  End Property
End Class
子ノードが追加済みかどうかを管理する機能を持つTreeNodeExクラス(VB.NET)

 上記のTreeNodeExオブジェクトを、ツリーに追加するノード・オブジェクトとして、TreeNodeオブジェクトの代わりに使えばよい。

効率的にツリーが構築されるサンプル・プログラム

 それでは、実際にBeforeExpandイベントでノードを追加するプログラムを作成してみよう。このサンプル・プログラムは、レジストリ・キーのツリー構造を表示するプログラムだ。

 以下の画面は、そのサンプル・プログラムを実行しているところである。

TreeViewコントロールのサンプル・プログラムの実行結果

●BeforeExpandイベント・ハンドラによる孫ノードの追加

 上のサンプル・プログラムのTreeViewコントロールのツリー・ノードを実際に追加している部分のコードを以下に抜粋して示す。

private void Form1_Load(object sender, System.EventArgs e)
{
  RefreshTreeView();
}

// TreeViewコントロールのデータを更新します。
private void RefreshTreeView()
{
  treeView1.Nodes.Clear();

  TreeNodeEx tne;
  tne = new TreeNodeEx("マイ コンピュータ");
  tne.Nodes.Add(new TreeNodeEx("HKEY_CLASSES_ROOT"));
  tne.Nodes.Add(new TreeNodeEx("HKEY_CURRENT_USER"));
  tne.Nodes.Add(new TreeNodeEx("HKEY_LOCAL_MACHINE"));
  tne.Nodes.Add(new TreeNodeEx("HKEY_USERS"));
  tne.Nodes.Add(new TreeNodeEx("HKEY_CURRENT_CONFIG"));
  tne.SubFoldersAdded = true;
  treeView1.Nodes.Add(tne);

  treeView1.TopNode.Expand();
}

private void treeView1_BeforeExpand(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
{
  // 1つ下の階層のすべてのノードに対して、
  // そのノードに子ノードが追加済みでない場合、
  // それを追加します。
  TreeNodeEx node = (TreeNodeEx)e.Node;
  foreach (TreeNodeEx child in node.Nodes)
  {
    if (child.SubFoldersAdded == false)
    {
      string[] arrayKeys =
        RegistryUtility.GetSubKeys(GetPathFromNode(child));
      foreach (string key in arrayKeys)
      {
        child.Nodes.Add(new TreeNodeEx(key));
      }
      child.SubFoldersAdded = true;
    }
  }
}

// ノードからパスを取得します。

private string GetPathFromNode(TreeNode node)
{
  if (node.Parent == null)
  {
    return node.Text;
  }
  return Path.Combine(GetPathFromNode(node.Parent), node.Text);
}
TreeViewコントロールにノードを効率的に追加するサンプル・コード(C#)
 
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

  RefreshTreeView()

End Sub

Private Sub RefreshTreeView()

  treeView1.Nodes.Clear()

  Dim tne As TreeNodeEx
  tne = New TreeNodeEx("マイ コンピュータ")
  tne.Nodes.Add(New TreeNodeEx("HKEY_CLASSES_ROOT"))
  tne.Nodes.Add(New TreeNodeEx("HKEY_CURRENT_USER"))
  tne.Nodes.Add(New TreeNodeEx("HKEY_LOCAL_MACHINE"))
  tne.Nodes.Add(New TreeNodeEx("HKEY_USERS"))
  tne.Nodes.Add(New TreeNodeEx("HKEY_CURRENT_CONFIG"))
  tne.SubFoldersAdded = True
  treeView1.Nodes.Add(tne)

  treeView1.TopNode.Expand()

End Sub

Private Sub treeView1_BeforeExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) Handles treeView1.BeforeExpand

  ' 1つ下の階層のすべてのノードに対して、
  ' そのノードに子ノードが追加済みでない場合、
  ' それを追加します。
  Dim node As TreeNodeEx = CType(e.Node, TreeNodeEx)
  Dim child As TreeNodeEx
  For Each child In node.Nodes
    If child.SubFoldersAdded = False Then
      Dim arrayKeys As String() = _
        RegistryUtility.GetSubKeys(GetPathFromNode(node))
      Dim key As String
      For Each key In arrayKeys
        child.Nodes.Add(New TreeNodeEx(key))
      Next key
      child.SubFoldersAdded = True
    End If
  Next child

End Sub

' ノードからパスを取得します。

Private Function GetPathFromNode(ByVal node As TreeNode) As String

  If node.Parent Is Nothing Then
    Return node.Text
  End If
  Return Path.Combine(GetPathFromNode(node.Parent), node.Text)

End Function
TreeViewコントロールにノードを効率的に追加するサンプル・コード(VB.NET)

 まず初期表示として(フォームのLoadイベント・ハンドラで)、RefreshTreeViewメソッドによりあらかじめ基本となるノードを追加する。そして、BeforeExpandイベントのイベント・ハンドラであるtreeView1_BeforeExpandメソッドで順次、必要なだけのノードを追加していく、という内容のコードになっている。

 RefreshTreeViewメソッドでは、TreeViewコントロールのNodesプロパティのAddメソッドにより、ツリーにノードを追加している。注意点としては、本稿では、追加するすべてのノードを前述のTreeNodeExオブジェクトにしているということだ。なおこのNodesプロパティのAddメソッドは、実際にはノードのコレクションを示すTreeNodeCollectionクラス(System.Windows.Forms名前空間)のメソッドである。詳しくは前掲のTIPSを参照してほしい。

 treeView1_BeforeExpandメソッドでは、そのパラメータに引き渡されるTreeViewCancelEventArgsオブジェクト(System.Windows.Forms名前空間)のNodeプロパティ(=「展開されようとしているノード」)のNodesプロパティからすべての「子ノード」を取得し、それぞれの子ノードが所有するサブ・キーを取得して、子ノードのNodesプロパティ(これも実体はTreeNodeCollectionオブジェクト)のAddメソッドにより、そのサブ・キーを子ノードの下に(「孫ノード」として)追加している。

 そのサブ・キーの取得では、RegistryUtilityクラスのGetSubKeysメソッドを利用している。RegistryUtilityクラスはレジストリからキー情報を得るために自作した本稿独自のクラスで、そのGetSubKeysメソッドはパラメータに指定したレジストリ・キー・パスの配下にあるすべてのサブ・キー名を文字列配列として返すメソッドである。また、レジストリ・キー・パスはGetPathFromNodeメソッドにより取得している。GetPathFromNodeメソッドは、パラメータに指定されたノードのパスを文字列として返すメソッドである。

 なおコードの実行時には、RefreshTreeViewメソッドの最後にTreeViewコントロールのTopNodeプロパティのExpandメソッドを呼び出しているため、ツリーに最初に追加した「マイ コンピュータ」というノードが展開される。この場合にもBeforeExpandイベントが発行されるので、初期の状態ですでに「マイコンピュータ」の子ノード(例えば、「HKEY_LOCAL_MACHINE」)に、そのサブ・キーのノード(「マイコンピュータ」から見ると、孫ノード)が追加されることになる。

 後はクリックなどによりノードを展開するたびに、そこで必要となるノードが追加され、ツリー全体が徐々に構築されることになる。End of Article

カテゴリ:Windowsフォーム 処理対象:TreeViewコントロール
使用ライブラリ:TreeNodeクラス(System.Windows.Forms名前空間)
使用ライブラリ:TreeNodeCollectionクラス(System.Windows.Forms名前空間)
使用ライブラリ:TreeViewCancelEventArgsクラス(System.Windows.Forms名前空間)
関連TIPS:TreeViewコントロールへ項目を追加するには?
 
この記事と関連性の高い別の.NET TIPS
TreeViewコントロールへ項目を追加するには?
TreeViewコントロールで現在選択されているノードを変更するには?
[ASP.NET]TreeViewコントロールで深階層のツリー情報を効率よく読み込むには?
[ASP.NET]TreeViewコントロールで深階層のツリー情報を効率よく読み込むには?
[ASP.NET]データベースからツリー・メニューを生成するには?
[ASP.NET]データベースからツリー・メニューを作成するには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間