Creating advanced data presentation in web applications often demands customized table layouts. In this post, we’ll walk through how to build a fully customized GridView with multi-level headers in ASP.NET Web Forms using C#.
We’ll cover:
-
Displaying a complex data structure (like grouped columns per category)
-
Dynamically injecting header rows
-
Binding SQL Server data to the GridView
Let’s get started!
📌 Scenario
You want to render a GridView
like this:
Hour | Date | 710 | 810 | TOTAL | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Total | A | B | C | Total | A | B | C | Total |
This is common in reporting dashboards where you have grouped data under certain categories (e.g., machine numbers, regions, etc.).
✅ Step 1: Define the GridView in ASPX
Use AutoGenerateColumns="false"
and add specific columns for each data field:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false"
HeaderStyle-BackColor="#9AD6ED" HeaderStyle-ForeColor="#000000"OnRowCreated="GridView1_RowCreated" CssClass="table table-bordered"><Columns><asp:BoundField DataField="xHour" HeaderText="Hour" /><asp:BoundField DataField="xDate" HeaderText="Date" /><!-- 710 Group --><asp:BoundField DataField="T710_Total" HeaderText="Total" /><asp:BoundField DataField="T710_A_RG" HeaderText="A_RG" /><asp:BoundField DataField="T710_B_RG" HeaderText="B_RG" /><asp:BoundField DataField="T710_C_RG" HeaderText="C_RG" /><!-- 810 Group --><asp:BoundField DataField="T810_Total" HeaderText="Total" /><asp:BoundField DataField="T810_A_RG" HeaderText="A_RG" /><asp:BoundField DataField="T810_B_RG" HeaderText="B_RG" /><asp:BoundField DataField="T810_C_RG" HeaderText="C_RG" /><!-- Total Column --><asp:BoundField DataField="Total" HeaderText="Total" /></Columns></asp:GridView>
✅ Step 2: Add a Multi-Row Header in Code-Behind
Now add an extra header row dynamically with OnRowCreated
.
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{if (e.Row.RowType == DataControlRowType.Header){GridViewRow headerRow = new GridViewRow(0, 0, DataControlRowType.Header, DataControlRowState.Insert);headerRow.Cells.Add(CreateHeaderCell("Hour", 1));headerRow.Cells.Add(CreateHeaderCell("Date", 1));headerRow.Cells.Add(CreateHeaderCell("710", 4));headerRow.Cells.Add(CreateHeaderCell("810", 4));headerRow.Cells.Add(CreateHeaderCell("TOTAL", 1));GridView1.Controls[0].Controls.AddAt(0, headerRow);}}private TableHeaderCell CreateHeaderCell(string text, int colspan){TableHeaderCell cell = new TableHeaderCell();cell.Text = text;cell.ColumnSpan = colspan;cell.HorizontalAlign = HorizontalAlign.Center;cell.BackColor = System.Drawing.ColorTranslator.FromHtml("#3AC0F2");cell.Font.Bold = true;return cell;}
This code inserts a custom header row above the default one, with merged cells grouped by category.
✅ Step 3: Retrieve and Bind SQL Server Data
Bind your stored procedure or SQL query results to the GridView:
protected void Page_Load(object sender, EventArgs e)
{if (!IsPostBack){BindGrid();}}private void BindGrid(){GridView1.DataSource = GetData();GridView1.DataBind();}private DataTable GetData(){DataTable dt = new DataTable();using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnSqlServer"].ConnectionString)){using (SqlCommand cmd = new SqlCommand("sp_C_CO", conn)){cmd.CommandType = CommandType.StoredProcedure;SqlDataAdapter da = new SqlDataAdapter(cmd);da.Fill(dt);}}return dt;}
Make sure your stored procedure returns column names like:
xHour, xDate, T710_Total, T710_A_RG, ..., T810_Total, ..., Total
🎨 Step 4: Optional Styling with CSS
You can add Bootstrap classes or your own CSS for better UI:
.table-bordered th, .table-bordered td {
border: 1px solid #ccc;padding: 8px;text-align: center;}