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

ASP.NET 實作下拉式樹狀選單:DataGridTree 與遞迴綁定完整方案

     編輯:ASP.NET 基礎

前言

在 Web 表單中呈現階層式資料(如部門架構、產品分類)時,下拉式樹狀選單是優雅的解決方案。本文介紹三種實作方式:DataGridTree 控制項繼承、遞迴綁定 DropDownList、介面導向的通用元件。

方案一:DataGridTree 表格樹控制項

特色

  • 繼承 `DataGrid`,支援固定欄位、固定表頭
  • 新增屬性:`TreeParentCode`(根節點代碼)、`TreeDisplayDeep`(預設展開層級)、`SumColumns`(自動彙總至根節點)
  • 客戶端 JS 支援:固定欄位/表頭、數值變更自動彙總父節點

核心遞迴建樹邏輯

private void MakeTree(DataTable dtNodes, string parentColumn, string rootValue, 
                      string indexColumn, string textColumn, DropDownList ddlBind, int level)
{
    level++;
    DataView dv = new DataView(dtNodes);
    dv.RowFilter = $"{parentColumn} = '{rootValue}'";
    
    string padding = new string(' ', level); // 全形空格縮排
    
    foreach (DataRowView drv in dv)
    {
        string nodeId = drv[indexColumn].ToString();
        string nodeText = padding + "├" + drv[textColumn].ToString();
        
        ddlBind.Items.Add(new ListItem(nodeText, nodeId));
        
        // 遞迴處理子節點
        MakeTree(dtNodes, parentColumn, nodeId, indexColumn, textColumn, ddlBind, level);
    }
    level--;
}

// 呼叫範例
private void CreateTree()
{
    string sql = "SELECT * FROM Master_Department WHERE Parent_Department = '003'";
    DataSet ds = DB.GetDS();
    MakeTree(ds.Tables[0], "Parent_Department", "003", 
             "Department_Code", "Department_Name", DropDownList1, -1);
}

方案二:介面導向通用下拉樹元件

設計理念

定義 `IDropDownTree` 介面,實作類別只需專注「取得子節點」邏輯,綁定、顯示邏輯由 `DropDownTree` 類別統一處理,符合單一職責原則。

介面定義

public interface IDropDownTree : IDisposable
{
    /// <summary>取得指定父節點的子節點集合</summary>
    Dictionary<string, string> GetChildCategory(string parentId);
    
    /// <summary>取得綁定器實例</summary>
    DropDownTree DropDownTree { get; }
}

通用綁定器 DropDownTree

public sealed class DropDownTree
{
    private readonly IDropDownTree _tree;

    public DropDownTree(IDropDownTree tree) => _tree = tree;

    /// <summary>產生樹狀前綴符號(├─、└─、│)</summary>
    private string GetPrefix(bool isLast, bool hasChild, string parentPrefix)
    {
        string result = "";
        if (!string.IsNullOrEmpty(parentPrefix))
        {
            // 移除最後一個符號,將 ├→│、└→空白
            result = parentPrefix.Substring(0, parentPrefix.Length - 1)
                         .Replace("├", "│").Replace("└", " ");
        }
        result += isLast ? "└" : "├";
        result += hasChild ? "┬" : "─";
        return result;
    }

    /// <summary>綁定至 DropDownList</summary>
    public void BindToDropDownList(DropDownList ddl, string removeId = null, string parentId = null, bool autoDispose = true)
    {
        if (ddl == null) return;

        string currentId = parentId ?? Guid.Empty.ToString();
        string currentSign = "", parentSign = "";
        bool hasChild = true;
        
        var parentKeyQueue = new Queue<string>();
        var parentSignQueue = new Queue<string>();
        int insertIndex = 0;

        while (hasChild)
        {
            int lastCount = 1;
            var children = _tree.GetChildCategory(currentId);
            
            if (children != null && children.Count > 0)
            {
                if (!string.IsNullOrEmpty(removeId) && children.ContainsKey(removeId))
                    children.Remove(removeId);

                foreach (var entry in children)
                {
                    bool hasGrandChild = _tree.GetChildCategory(entry.Key) != null;
                    currentSign = GetPrefix(lastCount == children.Count, hasGrandChild, parentSign);
                    
                    var item = new ListItem(currentSign + entry.Value, entry.Key);
                    
                    if (ddl.Items.Count > 0)
                        insertIndex = string.IsNullOrEmpty(currentId) ? insertIndex + 1 
                            : ddl.Items.IndexOf(ddl.Items.FindByValue(currentId)) + lastCount;
                    
                    ddl.Items.Insert(insertIndex, item);
                    lastCount++;

                    if (hasGrandChild)
                    {
                        parentKeyQueue.Enqueue(entry.Key);
                        parentSignQueue.Enqueue(currentSign);
                    }
                }

                if (parentKeyQueue.Count > 0)
                {
                    currentId = parentKeyQueue.Dequeue();
                    parentSign = parentSignQueue.Dequeue();
                }
                else
                {
                    hasChild = false;
                }
            }
            else
            {
                break;
            }
        }

        if (autoDispose) _tree.Dispose();
    }
}

實作類別範例

public class DepartmentService : IDropDownTree
{
    public Dictionary<string, string> GetChildCategory(string parentId)
    {
        string where = string.IsNullOrEmpty(parentId) 
            ? "ParentId IS NULL OR ParentId = '00000000-0000-0000-0000-000000000000'"
            : $"ParentId = '{parentId}'";
        
        var list = SelectList(0, where, "", false);
        if (list?.Count > 0)
        {
            return list.ToDictionary(x => x.Id.ToString(), x => x.CategoryName);
        }
        return null;
    }

    public DropDownTree DropDownTree => new DropDownTree(this);
    
    public void Dispose() { /* 釋放資源 */ }
    
    // 其他業務方法...
}

頁面呼叫

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        var service = new DepartmentService();
        service.DropDownTree.BindToDropDownList(ddlDepartment);
    }
}

方案三:前端 JS 動態載入(適合大量資料)

後端提供 JSON API,前端使用 zTree、jstree 或 Select2 + AJAX 實作按需載入,避免一次傳送大量 `

後端 API 範例

[WebMethod]
public static List<TreeNode> GetChildren(string parentId)
{
    // 回傳 { id, text, hasChildren } 結構
}

總結與選型建議

方案適用情境優點缺點
DataGridTree需表格式顯示、支援編輯、彙總功能豐富、伺服端渲染大量資料效能差、前端互動弱
遞迴綁定 DropDownList資料量 < 2000、單純選取實作簡單、無相依套件HTML 體積大、無搜尋/延遲載入
介面導向通用元件多處重用、架構導向專案解耦合、可測試、可替換實作初期建構較複雜
前端 JS 樹元件大量資料、需搜尋、複雜互動使用者體驗佳、分頁載入需前端技能、雙端開發

現代專案建議採用「後端 API + 前端元件」架構;維護舊專案時,介面導向元件提供良好的封裝與擴充性。

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