TreeView 是展示分類目錄、組織架構、菜單權限等層級數據的經典控件。本文提供基於遞歸算法的完整綁定方案,涵蓋數據庫表設計、後台遞歸構建樹節點、前端聲明式綁定,代碼結構清晰、易於擴展。
CREATE TABLE TreeViewName (
MenuID INT PRIMARY KEY IDENTITY(1,1),
MenuName NVARCHAR(100) NOT NULL,
ParentID INT DEFAULT 0, -- 0 表示根節點
SortIndex INT DEFAULT 0 -- 同級排序
);
| MenuID | MenuName | ParentID | SortIndex |
|---|---|---|---|
| 1 | 系統管理 | 0 | 1 |
| 2 | 用戶管理 | 1 | 1 |
| 3 | 角色管理 | 1 | 2 |
| 4 | 日志查詢 | 1 | 3 |
| 5 | 業務模塊 | 0 | 2 |
| 6 | 訂單管理 | 5 | 1 |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TreeViewDemo._Default" %>
<html xmlns="https://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<div>
<asp:TreeView ID="TreeView1" runat="server" ShowLines="True"
ExpandDepth="0" > <!-- 默認折疊 -->
</asp:TreeView>
</div>
</form>
</body>
</html>
private DataTable _dtSource;
private DataTable LoadTreeData()
{
string connStr = ConfigurationManager.ConnectionStrings["sql"].ConnectionString;
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand("SELECT * FROM TreeViewName ORDER BY SortIndex", conn))
using (var adapter = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
_dtSource = LoadTreeData();
BuildTree();
}
}
private void BuildTree()
{
// 1. 獲取根節點
DataRow[] roots = _dtSource.Select("ParentID = 0");
if (roots.Length == 0)
{
TreeView1.Nodes.Add(new TreeNode("無數據", "empty"));
return;
}
foreach (DataRow row in roots)
{
TreeNode node = CreateNode(row);
TreeView1.Nodes.Add(node);
BuildChildNodes(node);
}
}
private TreeNode CreateNode(DataRow row)
{
return new TreeNode
{
Text = row["MenuName"].ToString(),
Value = row["MenuID"].ToString(),
ToolTip = row["MenuID"].ToString(), // 攜帶 ID 便於後續處理
Expanded = false,
SelectAction = TreeNodeSelectAction.Expand // 點擊展開而非選中
};
}
private void BuildChildNodes(TreeNode parentNode)
{
string parentId = parentNode.Value;
DataRow[] children = _dtSource.Select($"ParentID = {parentId}");
foreach (DataRow row in children)
{
TreeNode child = CreateNode(row);
parentNode.ChildNodes.Add(child);
BuildChildNodes(child); // 遞歸處理下一層
}
}
// TreeView 節點展開事件:按需加載子節點
protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
if (e.Node.ChildNodes.Count == 1 && e.Node.ChildNodes[0].Value == "loading")
{
e.Node.ChildNodes.Clear(); // 移除佔位節點
LoadChildrenOnDemand(e.Node); // 從數據庫查詢並添加真實子節點
e.Node.PopulateOnDemand = false; // 標記已加載
}
}
// 前端需設置 PopulateOnDemand="true" 並添加佔位節點
private void CreateNodeWithLazy(DataRow row, TreeNode parent)
{
var node = CreateNode(row);
if (HasChildren(row["MenuID"]))
{
node.PopulateOnDemand = true;
node.ChildNodes.Add(new TreeNode("加載中...", "loading"));
}
parent.ChildNodes.Add(node);
}
protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
string menuId = TreeView1.SelectedNode.Value;
string menuName = TreeView1.SelectedNode.Text;
// 根據菜單 ID 加載對應內容頁面
Response.Redirect($"Content.aspx?menuId={menuId}");
}
| 方案 | 適用數據量 | 首屏速度 | 內存佔用 | 實現複雜度 |
|---|---|---|---|---|
| 全量遞歸(本文) | < 2000 節點 | 快 | 中 | 低 |
| 迭代構建 | < 5000 節點 | 快 | 中 | 中 |
| 懶加載 | 無上限 | 極快 | 低 | 高 |
本文提供的遞歸綁定方案代碼簡潔、邏輯清晰,適合中小型層級數據展示。對於大數據量、高併發場景,建議演進至懶加載模式或引入前端虛擬滾動樹組件(如 zTree、Vue Tree)配合 WebAPI 分頁接口,實現極致體驗與性能平衡。