合併原理:逐列掃描,比較當前列與後續列在指定欄位的值是否相同。相同則累計 RowSpan 計數並隱藏後續列儲存格;不同則中斷並移至下一組。
public static class GridViewExtensions
{
public static GridView RowSpan(this GridView gridView, object field)
{
var dict = ObjectToDictionary(field);
int columnIndex = int.Parse(dict["ColumnIndex"]);
string columnName = dict["ColumnName"]; // TemplateField 控制項 ID
string propertyName = dict["PropertyName"]; // 取值屬性
string columns = dict["Columns"]; // 同步合併欄位
for (int i = 0; i < gridView.Rows.Count; i++)
{
int rowSpanCount = 1;
for (int j = i + 1; j < gridView.Rows.Count; j++)
{
bool equal;
if (string.IsNullOrEmpty(columnName))
{
// 一般 BoundField
equal = gridView.Rows[i].Cells[columnIndex].Text ==
gridView.Rows[j].Cells[columnIndex].Text;
}
else
{
// TemplateField:透過 FindControl 取得控制項再取屬性值
var ctrl1 = gridView.Rows[i].Cells[columnIndex].FindControl(columnName);
var ctrl2 = gridView.Rows[j].Cells[columnIndex].FindControl(columnName);
equal = GetPropertyValue(ctrl1, propertyName)?.ToString() ==
GetPropertyValue(ctrl2, propertyName)?.ToString();
}
if (equal)
{
rowSpanCount++;
gridView.Rows[j].Cells[columnIndex].Visible = false;
if (!string.IsNullOrEmpty(columns))
{
columns.Split(',').ToList().ForEach(c =>
gridView.Rows[j].Cells[int.Parse(c)].Visible = false);
}
}
else break;
}
if (rowSpanCount > 1)
{
gridView.Rows[i].Cells[columnIndex].RowSpan = rowSpanCount;
if (!string.IsNullOrEmpty(columns))
{
columns.Split(',').ToList().ForEach(c =>
gridView.Rows[i].Cells[int.Parse(c)].RowSpan = rowSpanCount);
}
i += rowSpanCount - 1; // 跳過已合併的列
}
}
return gridView;
}
private static Dictionary<string, string> ObjectToDictionary(object obj)
{
var result = new Dictionary<string, string>();
var props = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var p in props)
result.Add(p.Name, p.GetValue(obj)?.ToString());
// 預設值
if (!result.ContainsKey("ColumnIndex")) throw new Exception("未指定 ColumnIndex");
if (!result.ContainsKey("ColumnName")) result.Add("ColumnName", null);
if (!result.ContainsKey("PropertyName")) result.Add("PropertyName", "Text");
if (!result.ContainsKey("Columns")) result.Add("Columns", null);
return result;
}
private static object GetPropertyValue(object obj, string propName)
{
if (obj == null) return null;
var pi = obj.GetType().GetProperty(propName);
return pi?.GetValue(obj);
}
}
i 走訪每列,內層 j 向下比對;合併完成後 i += rowSpanCount - 1 直接跳過已處理列。columnName 是否為空判斷模式,TemplateField 使用 FindControl + 反射取值。Columns 字串分割後,對每個索引同步設定 Visible = false 與 RowSpan。