合併儲存格原理:資料預先分組排序 → 遍歷列 → 相同值設 RowSpan 遞增、隱藏重複儲存格。必須在 DataBound 事件中執行,確保資料已繫結完成。
public static void MergeCells(GridView gv, int columnIndex)
{
if (gv.Rows.Count == 0) return;
string lastValue = gv.Rows[0].Cells[columnIndex].Text;
gv.Rows[0].Cells[columnIndex].RowSpan = 1;
int lastRowIndex = 0;
for (int i = 1; i < gv.Rows.Count; i++)
{
string currentValue = gv.Rows[i].Cells[columnIndex].Text;
if (currentValue == lastValue)
{
gv.Rows[i].Cells[columnIndex].Visible = false; // 隱藏重複
gv.Rows[lastRowIndex].Cells[columnIndex].RowSpan++;
}
else
{
lastValue = currentValue;
lastRowIndex = i;
gv.Rows[i].Cells[columnIndex].RowSpan = 1;
}
}
}
public static void MergeCells(DataGrid dg, int columnIndex)
{
if (dg.Items.Count == 0) return;
string lastValue = dg.Items[0].Cells[columnIndex].Text;
dg.Items[0].Cells[columnIndex].RowSpan = 1;
int lastRowIndex = 0;
for (int i = 1; i < dg.Items.Count; i++)
{
string currentValue = dg.Items[i].Cells[columnIndex].Text;
if (currentValue == lastValue)
{
dg.Items[i].Cells[columnIndex].Visible = false;
dg.Items[lastRowIndex].Cells[columnIndex].RowSpan++;
}
else
{
lastValue = currentValue;
lastRowIndex = i;
dg.Items[i].Cells[columnIndex].RowSpan = 1;
}
}
}
需透過 FindControl 取得控制項實例讀取值:
public static void MergeTemplateCells(GridView gv, int columnIndex, string controlId)
{
if (gv.Rows.Count == 0) return;
var firstCtrl = gv.Rows[0].Cells[columnIndex].FindControl(controlId) as Label;
string lastValue = firstCtrl?.Text ?? "";
gv.Rows[0].Cells[columnIndex].RowSpan = 1;
int lastRowIndex = 0;
for (int i = 1; i < gv.Rows.Count; i++)
{
var ctrl = gv.Rows[i].Cells[columnIndex].FindControl(controlId) as Label;
string currentValue = ctrl?.Text ?? "";
if (currentValue == lastValue)
{
gv.Rows[i].Cells[columnIndex].Visible = false;
gv.Rows[lastRowIndex].Cells[columnIndex].RowSpan++;
}
else
{
lastValue = currentValue;
lastRowIndex = i;
gv.Rows[i].Cells[columnIndex].RowSpan = 1;
}
}
}
protected void GridView1_DataBound(object sender, EventArgs e)
{
// 一般欄位:第 0、2 欄合併
MergeCells(GridView1, 0);
MergeCells(GridView1, 2);
// 模板欄位:第 1 欄,控制項 ID 為 "lblCategory"
MergeTemplateCells(GridView1, 1, "lblCategory");
}
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
foreach (TableCell cell in e.Row.Cells)
{
cell.Width = Unit.Pixel(120);
cell.Wrap = false; // 禁止換行
}
}
}
// DataGrid 對應寫法
protected void DataGrid1_ItemCreated(object sender, DataGridItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Header)
{
foreach (TableCell cell in e.Item.Cells)
{
cell.Width = Unit.Pixel(50);
cell.Wrap = false;
}
}
}
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
OnDataBound="GridView1_DataBound" OnRowCreated="GridView1_RowCreated">
<Columns>
<asp:BoundField DataField="Department" HeaderText="部門" />
<asp:TemplateField HeaderText="類別">
<ItemTemplate>
<asp:Label ID="lblCategory" runat="server" Text='<%# Eval("Category") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Status" HeaderText="狀態" />
<asp:BoundField DataField="Amount" HeaderText="金額" />
</Columns>
</asp:GridView>
| 限制 | 說明 | 解決方案 |
|---|---|---|
| 資料必須預先排序 | 相同值必須連續才能合併 | SQL `ORDER BY` 或 `DataView.Sort` |
| 分頁會中斷合併 | 每頁獨立計算 RowSpan | 不分頁或自訂跨頁合併邏輯 |
| RowSpan 跨越隱藏列 | 隱藏列仍佔據 DOM 空間 | 正常現象,CSS 控制視覺即可 |
| 編輯模式衝突 | 編輯時合併邏輯可能錯亂 | 編輯模式暫停合併 |
| 巢狀 GridView | 內層合併不影響外層 | 分別呼叫合併方法 |
若專案允許,建議改用前端元件庫:
透過 DataBound 事件遍歷列、比對值、設定 RowSpan 與 Visible=false,即可實作 GridView/DataGrid 儲存格合併。關鍵在於資料預先排序與正確取值方式 (一般欄位用 `.Text`,模板欄位用 `FindControl`)。現代專案建議前移合併邏輯至前端元件,後端僅負責輸出結構化資料。