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

C# 擴充方法實戰 (二):GridView 多欄位合併儲存格 (RowSpan) 通用元件

     編輯:關於C#

需求場景

GridView 顯示分組資料時,常需將連續相同值的儲存格垂直合併 (RowSpan),如「姓名」欄同組學生只顯示一次。本文實作一個泛用擴充方法 GridView.RowSpan(config),支援:

  • 繫結欄位 (BoundField) 與樣板欄位 兩種模式。
  • 單欄合併與多欄同步合併 (如姓名合併時,性別欄同步合併)。
  • 鏈式呼叫:gv.RowSpan(cfg1).RowSpan(cfg2)...。

擴充方法完整實作

public static class GridViewExtensions {
    public static GridView RowSpan(this GridView gv, object config) {
        var dict = ParseConfig(config);
        int colIdx = int.Parse(dict["ColumnIndex"]);
        string templateId = dict["ColumnName"]; // 模板欄位控制項 ID
        string propName = dict["PropertyName"]; // 模板控制項取值屬性 (預設 Text)
        string extraCols = dict["Columns"];     // 同步合併的額外欄位索引 (逗號分隔)

        for (int i = 0; i < gv.Rows.Count; i++) {
            int span = 1;
            for (int j = i + 1; j < gv.Rows.Count; j++) {
                bool equal;
                if (string.IsNullOrEmpty(templateId)) {
                    // 繫結欄位:直接比較 Cells.Text
                    equal = gv.Rows[i].Cells[colIdx].Text == gv.Rows[j].Cells[colIdx].Text;
                } else {
                    // 樣板欄位:透過 FindControl 取得控制項並比較屬性值
                    var ctrl1 = gv.Rows[i].Cells[colIdx].FindControl(templateId);
                    var ctrl2 = gv.Rows[j].Cells[colIdx].FindControl(templateId);
                    equal = GetProp(ctrl1, propName)?.ToString() == GetProp(ctrl2, propName)?.ToString();
                }

                if (equal) {
                    span++;
                    gv.Rows[j].Cells[colIdx].Visible = false;
                    // 隱藏同步合併欄位
                    if (!string.IsNullOrEmpty(extraCols))
                        extraCols.Split(',').ToList().ForEach(c => gv.Rows[j].Cells[int.Parse(c)].Visible = false);
                } else break;
            }

            if (span > 1) {
                gv.Rows[i].Cells[colIdx].RowSpan = span;
                if (!string.IsNullOrEmpty(extraCols))
                    extraCols.Split(',').ToList().ForEach(c => gv.Rows[i].Cells[int.Parse(c)].RowSpan = span);
                i += span - 1; // 跳過已合併列
            }
        }
        return gv;
    }

    static Dictionary ParseConfig(object cfg) {
        var dict = new Dictionary();
        foreach (var p in cfg.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
            dict[p.Name] = p.GetValue(cfg)?.ToString();

        if (!dict.ContainsKey("ColumnIndex")) throw new Exception("必須指定 ColumnIndex");
        dict.TryAdd("ColumnName", null);
        dict.TryAdd("PropertyName", "Text");
        dict.TryAdd("Columns", null);
        return dict;
    }

    static object GetProp(object obj, string prop) => obj?.GetType().GetProperty(prop)?.GetValue(obj);
}

使用範例

// 資料來源
var data = new[] {
    new { Name="張三", Gender="男", Chinese=86, Math=90, Term="上學期" },
    new { Name="張三", Gender="男", Chinese=89, Math=98, Term="下學期" },
    new { Name="李四", Gender="男", Chinese=89, Math=64, Term="上學期" },
    new { Name="李四", Gender="男", Chinese=75, Math=64, Term="下學期" },
    new { Name="王五", Gender="男", Chinese=89, Math=64, Term="上學期" },
    new { Name="王五", Gender="男", Chinese=63, Math=93, Term="下學期" }
};
GridView1.DataSource = data;
GridView1.DataBind();

// 合併第 0 欄 (姓名) 並同步合併第 1 欄 (性別)
GridView1.RowSpan(new { ColumnIndex = 0, Columns = "1" });

// 再合併第 2 欄 (國文)
GridView1.RowSpan(new { ColumnIndex = 2 });

// 樣板欄位範例:第 0 欄為 Label ID=lblName
// GridView1.RowSpan(new { ColumnIndex = 0, ColumnName = "lblName", PropertyName = "Text", Columns = "1" });

效果示意

姓名性別國文數學學期
張三男8690上學期
8998下學期
李四男8964上學期
7564下學期
王五男8964上學期
6393下學期

進階優化方向

  • 效能:大量資料時,雙重迴圈 O(n²) 可改用 Expression Tree 動態編譯比較委派,減少反射開銷。
  • 巢狀容器支援:目前 FindControl 僅搜尋直接子系,若控制項在 Panel 內需遞迴搜尋。
  • 複雜屬性路徑:支援 SelectedItem.Value 等巢狀屬性路徑解析。
  • 資料排序前提:合併邏輯假設資料已依目標欄位排序;未排序資料會產生錯誤合併。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved