|
.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...]という読み込み時のメッセージが表示され、読み込みが完了すると下位のノード群が表示されるはずだ。
|
generated by
|
|
Insider.NET 記事ランキング
本日
月間