從資料庫動態建構 TreeView 樹狀選單是企業應用常見需求。本文完整示範:資料表設計、遞迴建樹邏輯、圖示與導航 URL 綁定、WebForms 與 WinForms 兩種實作方式。
CREATE TABLE S_Menu (
NodeId CHAR(6) NOT NULL PRIMARY KEY, -- 節點唯一識別
ParentId CHAR(6) NOT NULL, -- 父節點 ID,根節點為 '0'
NodeName NVARCHAR(50) NOT NULL, -- 顯示文字
Address NVARCHAR(100) NULL, -- 導航 URL
Icon NVARCHAR(50) NULL, -- 圖示檔名
SortOrder INT DEFAULT 0, -- 同層排序
IsVisible BIT DEFAULT 1 -- 是否顯示
);
-- 範例資料
INSERT INTO S_Menu VALUES
('100000', '0', '公共查詢部', NULL, 'icon_folder.png', 1, 1),
('100001', '100000', '人民幣日報查詢', 'public/a1.aspx', 'icon_page.png', 1, 1),
('100002', '100000', '外幣日報查詢', 'public/a2.aspx', 'icon_page.png', 2, 1),
('100003', '0', '分行科技部', NULL, 'icon_folder.png', 2, 1),
('100004', '100003', '人民幣日報查詢', 'tech/a1.aspx', 'icon_page.png', 1, 1),
('100005', '100003', '外幣日報查詢', 'tech/a2.aspx', 'icon_page.png', 2, 1),
('100006', '0', '福田支行', NULL, 'icon_folder.png', 3, 1),
('100007', '100006', '月存款進度表', 'branch/a1.aspx', 'icon_page.png', 1, 1),
('100008', '100006', '月存款走勢圖', 'branch/a2.aspx', 'icon_page.png', 2, 1);
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
public class TreeViewBuilder
{
private readonly string _connectionString;
public TreeViewBuilder(string connectionString)
{
_connectionString = connectionString;
}
public void BuildTree(TreeView treeView)
{
treeView.Nodes.Clear();
treeView.ImageList = LoadImageList(); // 預載圖示
DataTable menuTable = GetMenuData();
if (menuTable.Rows.Count == 0) return;
// 建立節點查找字典:NodeId -> TreeNode
var nodeDict = new Dictionary();
// 第一輪:建立所有節點物件
foreach (DataRow row in menuTable.Rows)
{
if (!(bool)row["IsVisible"]) continue;
var node = new TreeNode
{
Text = row["NodeName"].ToString(),
Name = row["NodeId"].ToString(), // 使用 Name 屬性儲存 NodeId
Tag = row["Address"], // 使用 Tag 儲存 URL
ImageKey = row["Icon"]?.ToString() ?? "default",
SelectedImageKey = row["Icon"]?.ToString() ?? "default"
};
nodeDict[node.Name] = node;
}
// 第二輪:建立父子關係
foreach (var node in nodeDict.Values)
{
string parentId = GetParentId(menuTable, node.Name);
if (parentId == "0" || !nodeDict.ContainsKey(parentId))
{
// 根節點
treeView.Nodes.Add(node);
}
else
{
// 子節點
nodeDict[parentId].Nodes.Add(node);
}
}
// 展開第一層
foreach (TreeNode root in treeView.Nodes)
root.Expand();
}
private DataTable GetMenuData()
{
using var conn = new SqlConnection(_connectionString);
using var adapter = new SqlDataAdapter(
"SELECT NodeId, ParentId, NodeName, Address, Icon, SortOrder, IsVisible FROM S_Menu ORDER BY SortOrder",
conn);
var table = new DataTable();
adapter.Fill(table);
return table;
}
private string GetParentId(DataTable table, string nodeId)
{
var row = table.Rows.Cast().FirstOrDefault(r => r["NodeId"].ToString() == nodeId);
return row?["ParentId"]?.ToString() ?? "0";
}
private ImageList LoadImageList()
{
var imageList = new ImageList { ColorDepth = ColorDepth.Depth32Bit, ImageSize = new Size(16, 16) };
// 從資源或檔案載入圖示
// imageList.Images.Add("icon_folder.png", Properties.Resources.folder);
// imageList.Images.Add("icon_page.png", Properties.Resources.page);
return imageList;
}
}
// 使用方式
public partial class MainForm : Form
{
private void MainForm_Load(object sender, EventArgs e)
{
var builder = new TreeViewBuilder(ConfigurationManager.ConnectionStrings["Default"].ConnectionString);
builder.BuildTree(treeView1);
}
private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (e.Node.Tag is string url && !string.IsNullOrEmpty(url))
{
NavigateToUrl(url);
}
}
}
// menu_Left.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="menu_Left.aspx.cs" Inherits="App.Menu.Left" %>
// menu_Left.aspx.cs
using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI.WebControls;
namespace App.Menu
{
public partial class Left : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindRootNodes();
}
}
private void BindRootNodes()
{
using var conn = new SqlConnection(GetConnectionString());
using var cmd = new SqlCommand(
"SELECT NodeId, NodeName, Address, Icon FROM S_Menu WHERE ParentId = '0' AND IsVisible = 1 ORDER BY SortOrder",
conn);
conn.Open();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var node = new TreeNode
{
Text = reader["NodeName"].ToString(),
Value = reader["NodeId"].ToString(),
NavigateUrl = reader["Address"]?.ToString(),
ImageUrl = "~/Images/" + reader["Icon"]?.ToString(),
PopulateOnDemand = true // 啟用延遲載入
};
tvMenu.Nodes.Add(node);
}
}
protected void tvMenu_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
// 延遲載入子節點
string parentId = e.Node.Value;
using var conn = new SqlConnection(GetConnectionString());
using var cmd = new SqlCommand(
"SELECT NodeId, NodeName, Address, Icon FROM S_Menu WHERE ParentId = @ParentId AND IsVisible = 1 ORDER BY SortOrder",
conn);
cmd.Parameters.AddWithValue("@ParentId", parentId);
conn.Open();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var child = new TreeNode
{
Text = reader["NodeName"].ToString(),
Value = reader["NodeId"].ToString(),
NavigateUrl = reader["Address"]?.ToString(),
ImageUrl = "~/Images/" + reader["Icon"]?.ToString(),
PopulateOnDemand = HasChildren(reader["NodeId"].ToString())
};
e.Node.ChildNodes.Add(child);
}
}
private bool HasChildren(string nodeId)
{
using var conn = new SqlConnection(GetConnectionString());
using var cmd = new SqlCommand(
"SELECT COUNT(1) FROM S_Menu WHERE ParentId = @NodeId AND IsVisible = 1", conn);
cmd.Parameters.AddWithValue("@NodeId", nodeId);
conn.Open();
return (int)cmd.ExecuteScalar() > 0;
}
private string GetConnectionString() =>
ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
}
}
public void BuildTreeRecursive(TreeNodeCollection nodes, string parentId, DataView dataView)
{
dataView.RowFilter = $"ParentId = '{parentId}'";
foreach (DataRowView rowView in dataView)
{
var node = new TreeNode
{
Text = rowView["NodeName"].ToString(),
Name = rowView["NodeId"].ToString(),
Tag = rowView["Address"],
ImageKey = rowView["Icon"]?.ToString()
};
nodes.Add(node);
// 遞迴建立子樹
BuildTreeRecursive(node.Nodes, node.Name, dataView);
}
}
// 使用
DataView dv = menuTable.DefaultView;
BuildTreeRecursive(treeView.Nodes, "0", dv);
資料庫驅動的 TreeView 實現靈活、可維護,配合延遲載入與快取策略,能支撐企業級應用的複雜導航結構需求。