首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# TreeView 資料庫驅動樹狀選單完整實作

     編輯:關於C#

從資料庫動態建構 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);

WinForms 實作

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);
        }
    }
}

WebForms (ASP.NET) 實作

// 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;
    }
}

遞迴建樹通用邏輯 (C# 通用)

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);

效能優化策略

| 策略 | 說明 | 適用場景 | |------|------|----------| | **延遲載入** | `PopulateOnDemand` / 按需遞迴 | 大型樹狀結構 (>1000 節點) | | **快取資料表** | `DataTable` 快存,減少 DB 查詢 | 頻繁重建樹、多使用者共用結構 | | **虛擬模式** | WinForms `TreeView.VirtualMode` | 極大資料集 (>10000 節點) | | **非同步載入** | `async/await` + `BeginUpdate/EndUpdate` | 防止 UI 凍結 | | **分頁/搜尋** | 僅載入可見範圍、提供搜尋 | 使用者不需一次看完整棵樹 |

常見問題排查

| 問題 | 解決方案 | |------|----------| | 循環參考導致堆疊溢位 | 資料檢查:遞迴前驗證無循環 (CTE 查詢檢測) | | 圖示不顯示 | `ImageList` 未指派、圖示 Key 不匹配、路徑錯誤 | | 節點重複 | `NodeId` 唯一性約束、建樹邏輯去重 | | 中文亂碼 | 連線字串加 `Charset=utf8`、資料庫欄位 `NVARCHAR` | | 展開/摺疊效能差 | `BeginUpdate()`/`EndUpdate()` 包裹批次操作 |

資料庫驅動的 TreeView 實現靈活、可維護,配合延遲載入與快取策略,能支撐企業級應用的複雜導航結構需求。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved