.NET TIPS

[ASP.NET]TreeViewコントロールで深階層のツリー情報を効率よく読み込むには?

山田 祥寛
2005/09/09

 「TIPS:[ASP.NET]データベースからツリー・メニューを生成するには?」でも紹介したように、TreeViewコントロールを利用することで、データベース上のコンテンツ情報から動的にツリー形式のメニューを生成することが可能になる。

 しかし、前掲のTIPSで紹介した内容には、1つ問題がある。というのも、サイトの規模によっては、コンテンツ情報も(当然)膨大な分量になり、また階層も深くなる可能性がある。このようなサイトにおいて、初回起動時にすべてのツリー・ノードを展開しようとすると、処理にも時間がかかり、ひいてはアプリケーション全体のパフォーマンスを悪化させる原因になるだろう。

 そこで本稿では、TreeViewコントロールをドリルダウンするタイミングで、必要なノードのみを展開する方法を紹介する。本稿のテクニックを利用することで、深階層のツリー情報を表示する場合にも、より高いパフォーマンスを期待できる。

 なお、本稿のサンプル・アプリケーションを利用するには、あらかじめ「TIPS:[ASP.NET]TreeViewコントロールでツリー・メニューを作成するには?」の内容に従って、Internet Explorer WebBrowserコントロールをインストールし、また、前掲の「TIPS:[ASP.NET]データベースからツリー・メニューを生成するには?」に従って、データベース上にsitemapテーブルを作成しておく必要がある。

 それではさっそく、具体的なコードを見ていくことにしよう。

<%@ Page ContentType="text/html" Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Register TagPrefix="ie" Namespace="Microsoft.Web.UI.WebControls"
             Assembly="Microsoft.Web.UI.WebControls" %>
<script runat="Server">

// ページが初回ロードされたタイミングでツリーの最上位ノードを取得
void Page_Load(Object sender, EventArgs e) {
  if (!Page.IsPostBack) {
    SetNewNode("-", tree.Nodes);
  }
}

// ツリーの各ノードが展開されたタイミングで実行
void tree_Expand(Object sender, TreeViewClickEventArgs e) {
  TreeNode tmpNode = null;
  // イベント発生元のノードを取得
  TreeNodeCollection tmpNodes = tree.Nodes;
  String[] nodeStep = e.Node.Split('.');
  For (int i = 0; i <= nodeStep.GetUpperBound(0); i++){
    tmpNode = tmpNodes[Int32.Parse(nodeStep[i])];
    tmpNodes = tmpNode.Nodes;
  }
  // イベント発生元のノードを親に持つノード群をセット
  SetNewNode(tmpNode.NavigateUrl, tmpNodes);
}

// 指定されたノード(parent)を親に持つ子ノード群をセット
void SetNewNode(String parent, TreeNodeCollection nodes) {

  // 指定ノードにすでに子ノードが追加されていない場合にのみ
  // 以下の処理を実行
  If (nodes.Count == 0) {
    SqlConnection db = new SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info=True;Initial Catalog=dotnet");

    // 指定されたノードを親に持つ子ノード情報を取得
    SqlCommand comm = new SqlCommand("SELECT url,title,target FROM sitemap WHERE parent=@parent", db);
    comm.Parameters.Add("@parent", parent);
    db.Open();
    SqlDataReader reader = comm.ExecuteReader();

    // 取得したDataReaderの内容をノード(TreeNodeオブジェクト)
    // として追加
    while (reader.Read()) {
      TreeNode node = new TreeNode();
      node.NavigateUrl = reader.GetString(0);

      // 取得したノードに子ノードが存在しない場合、ノード型を
      // “Folder”に、かつ、ノードを展開可能な状態にセット。
      // さもなければ、ノード型を“File”とする
      if (hasChildNodes(reader.GetString(0))) {
        node.Type = "Folder";
        node.Expandable = ExpandableValue.Always;
      } else {
        node.Type = "File";
      }
      node.Text = reader.GetString(1);
      node.Target = reader.GetString(2);
      nodes.Add(node);
    }
    db.Close();
  }
}

// 指定されたノードを親とする子ノードが存在するかをチェック
bool hasChildNodes(String parent) {
  bool flag = false;
  SqlConnection db = new SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info=True;Initial Catalog=dotnet");

  // 指定されたノードをキーに、子ノードを検索
  SqlCommand comm = new SqlCommand("SELECT url FROM sitemap WHERE parent=@parent", db);
  comm.Parameters.Add("@parent", parent);
  db.Open();
  SqlDataReader reader = comm.ExecuteReader();

  // マッチしたノードが存在した場合にのみフラグ変数flagにtrueをセット
  if (reader.HasRows) { flag = true; }
  db.Close();
  return flag;
}
</script>
<html>
<head>
<title>データベースからツリーメニューを生成</title>
</head>
<body>
<form runat="Server">
<ie:TreeView id="tree" runat="Server" AutoPostBack="True"
  SystemImagesPath="/webctrl_client/1_0/treeimages/"
  OnExpand="tree_Expand">
  <ie:treenodetype Type="Folder"
    ExpandedImageUrl="/webctrl_client/1_0/images/folderopen.gif"
    ImageUrl="/webctrl_client/1_0/images/folder.gif" />
  <ie:TreeNodeType Type="File"
    ImageUrl="/webctrl_client/1_0/images/html.gif" />
</ie:TreeView>
</form>
</body>
</html>
深階層のツリー情報を読み込むために最適化されたWebフォーム(C#版:treeview_perform_cs.aspx)
 
<%@ Page ContentType="text/html" Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Register TagPrefix="ie" Namespace="Microsoft.Web.UI.WebControls"
             Assembly="Microsoft.Web.UI.WebControls" %>
<script runat="Server">

' ページが初回ロードされたタイミングでツリーの最上位ノードを取得
Sub Page_Load(sender As Object, e As EventArgs)
  If Not Page.IsPostBack Then
    SetNewNode("-", tree.Nodes)
  End If
End Sub

' ツリーの各ノードが展開されたタイミングで実行
Sub tree_Expand(sender As Object, e As TreeViewClickEventArgs)
  Dim tmpNode As TreeNode
  ' イベント発生元のノードを取得
  Dim tmpNodes As TreeNodeCollection = tree.Nodes
  Dim nodeStep As String() = e.Node.Split(".")
  For i As Integer = 0 To nodeStep.GetUpperBound(0)
    tmpNode = tmpNodes.Item(nodeStep(i))
    tmpNodes = tmpNode.Nodes
  Next
  ' イベント発生元のノードを親に持つノード群をセット
  SetNewNode(tmpNode.NavigateUrl,tmpNodes)
End Sub

' 指定されたノード(parent)を親に持つ子ノード群をセット
Sub SetNewNode(parent As String, nodes As TreeNodeCollection)
  ' 指定ノードにすでに子ノードが追加されていない場合にのみ
  '以下の処理を実行
  If nodes.Count = 0 Then
    Dim db As New SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info=True;Initial Catalog=dotnet")

    ' 指定されたノードを親に持つ子ノード情報を取得
    Dim comm As New SqlCommand("SELECT url,title,target FROM sitemap WHERE parent=@parent", db)
    comm.Parameters.Add("@parent", parent)
    db.Open()
    Dim reader As SqlDataReader = comm.ExecuteReader()

    ' 取得したDataReaderの内容をノード(TreeNodeオブジェクト)
    'として追加
    Do While reader.Read()
      Dim node As New TreeNode()
      node.NavigateUrl = reader.GetString(0)
      node.Type="Folder"

      ' 取得したノードに子ノードが存在しない場合、ノード型を
      ' “Folder”に、かつ、ノードを展開可能な状態にセット。
      ' さもなければ、ノード型を“File”とする
      If hasChildNodes(reader.GetString(0)) Then
        node.Type = "Folder"
        node.Expandable = ExpandableValue.Always
      Else
        node.Type = "File"
      End If
      node.Text = reader.GetString(1)
      node.Target = reader.GetString(2)
      nodes.Add(node)
    Loop
    db.Close()
  End If
End Sub

' 指定されたノードを親とする子ノードが存在するかをチェック
Function hasChildNodes(parent As String) As Boolean
  Dim flag As Boolean = False
  Dim db As New SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info=True;Initial Catalog=dotnet")

  ' 指定されたノードをキーに、子ノードを検索
  Dim comm As New SqlCommand("SELECT url FROM sitemap WHERE parent=@parent", db)
  comm.Parameters.Add("@parent", parent)
  db.Open()
  Dim reader As SqlDataReader = comm.ExecuteReader()
  ' マッチしたノードが存在した場合にのみフラグ変数flagにtrueをセット
  If reader.HasRows Then flag = True
  db.Close()
  Return flag
End Function
</script>
<html>
<head>
<title>データベースからツリーメニューを生成</title>
</head>
<body>
<form runat="Server">
<ie:TreeView id="tree" runat="Server" AutoPostBack="True"
  SystemImagesPath="/webctrl_client/1_0/treeimages/"
  OnExpand="tree_Expand">
  <ie:treenodetype Type="Folder"
    ExpandedImageUrl="/webctrl_client/1_0/images/folderopen.gif"
    ImageUrl="/webctrl_client/1_0/images/folder.gif" />
  <ie:TreeNodeType Type="File"
    ImageUrl="/webctrl_client/1_0/images/html.gif" />
</ie:TreeView>
</form>
</body>
</html>
深階層のツリー情報を読み込むために最適化されたWebフォーム(VB.NET版:treeview_perform_vb.aspx)

 上記のコードでポイントとなるのは、下位ノードの読み込みをExpandイベントの発生したタイミングで行っているという点だ。Expandイベントは、ツリーの各ノードをドリルダウンしたタイミングで発生する。本稿のサンプル・プログラムでは、Expandイベントに対応するイベント・ハンドラはtree_Expandメソッドだ。なお、Expandイベントをリアルタイムに捕捉するには、必ずTreeViewコントロールのAutoPostBackプロパティがtrueに設定されている必要がある。

 tree_Expandメソッドでは、第2パラメータに引き渡されるTreeViewClickEventArgsオブジェクトのNodeプロパティで、ドリルダウンしたノードがString型の値として返される。例えば、Nodeプロパティの値が「0.2」であれば、1番目のノード配下の3番目の子ノードであることを意味する(以降、階層が深くなった場合にも「.」連結で同様の記述)。つまり、ここではNodeプロパティの戻り値を「.」で分割し、各階層のノードを走査することで、最終的にイベントの発生元となったTreeNodeオブジェクトを取得しているというわけだ。

 SetNewNodeメソッドは、取得したTreeNodeオブジェクトに属する子ノード群を取得し、現在のツリーに追加する。基本的なロジックは、前掲の「TIPS:[ASP.NET]データベースからツリー・メニューを生成するには?」とほぼ同等であるので、詳細はそちらを参照していただきたい。

 本稿で注目していただきたいのは、子ノード追加時にhasChildNodesメソッドでさらに配下のノード群が存在するかどうかを判定しているという点だ。配下にノードが存在するかどうかに応じて、ノードのタイプと展開の可否を子ノードに設定している。展開の可否を設定するExpandableプロパティがデフォルトのExpandableValue.Autoであり、かつ、配下の子ノードがまだ設定されていないケースでは、ノードは展開不可の状態となってしまうので注意すること。また、Expandableプロパティに指定可能なExpandableValue構造体のメンバを以下に示しておく。

メンバ名 概要
Always 子ノードの有無にかかわらず、常にノードは展開可能
Auto 子ノードが存在する場合にのみ、ノードは展開可能(デフォルト)
CheckOnce 初回呼び出し時は展開可能。ただし、ユーザーが最初にクリックしたときに子ノードが見つからなければ展開不可に変更される
Expandableプロパティで設定可能な値(ExpandableValue構造体のメンバ)

 以上が理解できたら、さっそく、サンプル・コードを実行してみよう。データベースに格納した内容に従って、以下のようなツリーが表示されれば成功だ。

サンプル・プログラムの実行結果
ノード「関連資料」をクリックして展開したところ。ノードをドリルダウンしたタイミングで、動的に下位ノードを取得していることが確認できる。

 展開可能なノードをクリックすると、[Retrieving nodes...]という読み込み時のメッセージが表示され、読み込みが完了すると下位のノード群が表示されるはずだ。End of Article

カテゴリ:Webフォーム 処理対象:IE WebBrowserコントロール
関連TIPS:[ASP.NET]データベースからツリー・メニューを生成するには?
関連TIPS:[ASP.NET]TreeViewコントロールでツリー・メニューを作成するには?
 
この記事と関連性の高い別の.NET TIPS
[ASP.NET]TreeViewコントロールで深階層のツリー情報を効率よく読み込むには?
TreeViewコントロールで効率的にツリーを構築するには?
TreeViewコントロールへ項目を追加するには?
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 記事ランキング

本日 月間