GridView 顯示分組資料時,常需將連續相同值的儲存格垂直合併 (RowSpan),如「姓名」欄同組學生只顯示一次。本文實作一個泛用擴充方法 GridView.RowSpan(config),支援:
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" });
| 姓名 | 性別 | 國文 | 數學 | 學期 |
|---|---|---|---|---|
| 張三 | 男 | 86 | 90 | 上學期 |
| 89 | 98 | 下學期 | ||
| 李四 | 男 | 89 | 64 | 上學期 |
| 75 | 64 | 下學期 | ||
| 王五 | 男 | 89 | 64 | 上學期 |
| 63 | 93 | 下學期 |
Expression Tree 動態編譯比較委派,減少反射開銷。FindControl 僅搜尋直接子系,若控制項在 Panel 內需遞迴搜尋。SelectedItem.Value 等巢狀屬性路徑解析。