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

ASP.NET TreeView 建構多層級複選輸入介面:級聯選取、彈出視窗、資料綁定與回傳值組裝

     編輯:ASP.NET 基礎

適用場景與設計考量

當輸入資料具備層級結構 (如產品分類、地區行政區劃、業務範圍) 且需多選時,DropDownList/CheckBoxList 難以勝任。TreeView + CheckBox 提供直觀的樹狀多選體驗,配合 JavaScript 級聯選取、伺服器端遞迴資料綁定、彈出視窗回傳值組裝,構建完整輸入方案。

前端介面:TreeView + JavaScript 級聯

<asp:TreeView ID="TreeView1" runat="server" 
    ShowCheckBoxes="All" ShowLines="True" 
    ExpandDepth="1" Font-Bold="False" ForeColor="#0000CC"
    onclick="OnTreeNodeChecked();">
</asp:TreeView>

<script>
// 級聯選取:父節點勾選/取消 → 所有子節點跟隨
function OnTreeNodeChecked() {
    var el = event.srcElement;
    if (el.type !== 'checkbox') return;
    
    var childrenDivId = el.id.replace('CheckBox', 'Nodes');
    var div = document.getElementById(childrenDivId);
    if (!div) return;
    
    var checkboxes = div.getElementsByTagName('INPUT');
    for (var i = 0; i < checkboxes.length; i++) {
        if (checkboxes[i].type === 'checkbox')
            checkboxes[i].checked = el.checked;
    }
}

// 彈出視窗確定:收集所有勾選值 → 組裝格式化字串 → 回傳父視窗
function SubmitValue() {
    var vals = [];
    var inputs = document.getElementsByTagName('INPUT');
    for (var i = 0; i < inputs.length; i++) {
        if (inputs[i].type === 'checkbox' && inputs[i].checked)
            vals.push(inputs[i].value);
    }
    window.returnValue = vals.join(',');
    window.close();
}
</script>

後端資料綁定:遞迴建構樹節點

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack) BindData();
}

private void BindData()
{
    // 1. 從 BLL 取得樹狀資料結構
    ArrayList scopeTree = BLLFactory<BusinessScope>.Instance.GetTree();
    
    // 2. 遞迴建立 TreeNode
    foreach (BusinessScopeNodeInfo nodeInfo in scopeTree)
    {
        var rootNode = new TreeNode(nodeInfo.Name);
        rootNode.SelectAction = TreeNodeSelectAction.Expand; // 點擊文字展開而非導航
        TreeView1.Nodes.Add(rootNode);
        AddTreeNode(rootNode, nodeInfo);
    }
}

private void AddTreeNode(TreeNode parentNode, BusinessScopeNodeInfo nodeInfo)
{
    foreach (BusinessScopeNodeInfo child in nodeInfo.Children)
    {
        var node = new TreeNode(child.Name);
        node.SelectAction = TreeNodeSelectAction.Expand;
        parentNode.ChildNodes.Add(node);
        AddTreeNode(node, child); // 遞迴
    }
}

資料存取層:單次查詢 + 記憶體遞迴組裝

避免遞迴查詢資料庫 (N+1 問題),採用一次撈取所有節點 → DataTable.Select 記憶體篩選 → 遞迴建構物件樹:

public ArrayList GetTree()
{
    var result = new ArrayList();
    string sql = $"SELECT * FROM {tableName} ORDER BY PID, Seq";
    var ds = db.ExecuteDataSet(db.GetSqlStringCommand(sql));
    
    if (ds.Tables.Count > 0)
    {
        DataTable dt = ds.Tables[0];
        // 取得根節點 (PID = -1)
        DataRow[] roots = dt.Select("PID = -1");
        
        foreach (DataRow row in roots)
        {
            int id = Convert.ToInt32(row["ID"]);
            result.Add(BuildNode(id, dt));
        }
    }
    return result;
}

private BusinessScopeNodeInfo BuildNode(int id, DataTable dt)
{
    var info = FindByID(id); // 取得單一節點資料
    var node = new BusinessScopeNodeInfo(info);
    
    // 記憶體中找子節點
    DataRow[] children = dt.Select($"PID = {id}");
    foreach (DataRow row in children)
    {
        int childId = Convert.ToInt32(row["ID"]);
        node.Children.Add(BuildNode(childId, dt));
    }
    return node;
}

實體類別設計:節點資訊封裝

[Serializable]
public class BusinessScopeInfo : BaseEntity
{
    public virtual decimal Id { get; set; }
    public virtual decimal Pid { get; set; } = -1;
    public virtual string Name { get; set; } = "";
    public virtual string Seq { get; set; } = "";
}

// 節點專用:包含子節點集合
public class BusinessScopeNodeInfo : BusinessScopeInfo
{
    private ArrayList _children = new ArrayList();
    public ArrayList Children 
    { 
        get => _children; 
        set => _children = value ?? new ArrayList(); 
    }
    
    public BusinessScopeNodeInfo() { }
    public BusinessScopeNodeInfo(BusinessScopeInfo info)
    {
        Id = info.Id; Name = info.Name; Seq = info.Seq;
    }
}

確定按鈕:後端遍歷組裝回傳值

protected void btnSelect_Click(object sender, ImageClickEventArgs e)
{
    var result = new StringBuilder();
    
    foreach (TreeNode parent in TreeView1.Nodes)
    {
        foreach (TreeNode node in parent.ChildNodes)
        {
            var sb = new StringBuilder();
            // 收集第三層勾選項
            foreach (TreeNode sub in node.ChildNodes)
            {
                if (sub.Checked) sb.AppendFormat("{0},", sub.Text);
            }
            
            if (sb.Length > 0)
            {
                // 格式:父節點(子項1,子項2,...);
                sb.Insert(0, $"{node.Text}(");
                sb.Append(")");
                result.Append(sb.ToString().Replace(",)", ")") + ";");
            }
            else if (node.Checked)
            {
                // 第二層直接勾選無第三層
                result.Append(node.Text + ";");
            }
        }
    }
    
    // 透過 Helper 關閉視窗並回傳值
    Helper.CloseWin(this, result.ToString().TrimEnd(';'));
}

父視窗接收回傳值

<asp:TextBox ID="txtScope" runat="server" ReadOnly="true" Width="400px" />
<asp:Button ID="btnOpenScope" runat="server" Text="選擇範圍" 
    OnClientClick="window.open('ScopeSelector.aspx','_blank','width=500,height=400');return false;" />

<script>
function ReceiveScopeValue(value) {
    document.getElementById('<%=txtScope.ClientID%>').value = value;
}
</script>

進階優化建議

痛點優化方案
大量節點渲染緩慢虛擬滾動 / 延遲載入 (AJAX 按需載入子節點) / 改用 jsTree / zTree 前端元件
ViewState 膨脹TreeView.ViewStateMode = Disabled;或改用純前端樹 + HiddenField 存值
級聯邏輯僅限前端後端驗證時同步檢查父子邏輯一致性
資料來源變更頻繁快取樹結構 (MemoryCache/Redis),依賴 SqlCacheDependency 或訊息佇列失效
行動裝置操作困難改用 Select2/Chosen 下拉多選 + 群組標籤 模擬層級

現代化替代架構

// WebAPI 端點
[HttpGet("api/scopes/tree")]
public async Task<IEnumerable<ScopeTreeDto>> GetScopeTree()
{
    var all = await _repo.GetAllAsync();
    return BuildTree(all.Where(x => x.Pid == -1), all);
}

// 前端 Vue/React + Element Plus / Ant Design Tree
<el-tree 
  :data="scopeTree" 
  show-checkbox 
  node-key="id" 
  check-strictly="false" 
  :props="{ children: 'children', label: 'name' }"
  @check-change="onCheckChange">
</el-tree>

總結

ASP.NET TreeView 多選方案展示了 WebForms 時代伺服器端控制項 + 客戶端腳本協作的經典模式:後端遞迴綁定樹結構、前端 JS 處理級聯邏輯、後端遍歷組裝格式化回傳值。雖然現代專案多改用前端樹狀元件 + API,但此方案在零前端建置、快速開發內部系統、相容舊瀏覽器場景下仍具高效率。關鍵成功因素:單次查詢組裝記憶體樹、CheckBox 級聯邏輯、格式化回傳值協定。

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