Adding (or Inserting) Subheader Rows into a Gridview
Ever come across a situation where you needed to manually add (or insert) rows to a Gridview, but weren’t quite sure how to do it? A few weeks ago I came across this exact problem, where I needed to create sub headers based on the category for each row. Here’s how I did it.
So here’s the problem that I’m trying to solve. As you can see in the first image, I have a table of restaurants with the second column containing the category that the restaurant falls under (Fast Food, Italian, Pizza, Japanese, ….).

This second image contains the exact same, unmodified listing of restaurants. The difference being that the restaurants are grouped under a Restaurant Category sub heading. 
When I first tried to solve this problem, I thought that it would be pretty easy. The Gridview.Rows must have an Add() (or Insert()) method that I could call and pass in a GridviewRow object, right?
Wrong. Unfortunately to my surprise, the Gridview doesn’t support this ability.
After a bit of trial and error, and some Googling, I came up with a somewhat simple way to accomplish this problem, and I’m going to show you how.
So let’s begin. First off, we’ll need a DataSet of some data to display in the Gridview. This can be anything you’d like, as long as there is some sort of category field. For me, I manually created a DataSet of restaurants which includes a “category” field. This can be named anything you’d like.
Below is a partial screenshot of my DataSet. 
Next, we’ll need a Gridview. Here is what I have on my page. Nothing too fancy, just a regular Gridview with a few columns and some simple styling.
<h3>Restaurant Listing</h3> <asp:gridview id=“gdvRestaurants” autogeneratecolumns=“false” emptydatatext=“No Restaurants were found.” onrowdatabound=“gdvRowDatabound” cellpadding=“5″ width=“860″ runat=“server”> <headerstyle backcolor=“#507CD1″ forecolor=“white” /> <rowstyle font-size=“Small” /> <alternatingrowstyle backcolor=“#DCE4F9″ /> <columns> <asp:boundfield datafield=“RestaurantId” headertext=“Id” /> <asp:boundfield datafield=“Category” headertext=“Category” /> <asp:boundfield datafield=“Name” headertext=“Name” /> <asp:boundfield datafield=“Phone” headertext=“Phone” /> <asp:boundfield datafield=“Address” headertext=“Address” /> <asp:boundfield datafield=“Zip” headertext=“Zip” /> </columns> </asp:gridview>
Pretty harmless. If you’re confused about anything in the above code, let me briefly explain. AutoGenerateColumns has been set to false because I want to add my own columns to display. I’ve also added a EmptyDataText just in case there’s nothing to display.
Next I’ve added a OnRowDatabound attribute and set it to gdvRowDataBound, which is the name of the method that we will create shortly.
After that, there’s some simple styling, such as cellpadding, forecolor, backcolor, width and font-size. These should all be pretty self explanatory.
Then I’ve added several columns to be displayed, and this is why I set AutoGenerateColumns to false.
Since we already specified the method to be called when the OnRowDatabound event is raised, we’ll need to add that event to our code behind/beside page (*.aspx.cs for C# or *.aspx.vb for Visual basic).
protected void gdvRowDatabound(object sender, GridViewRowEventArgs e) { }
Unlike MTV’s show “Cribs”, this is were the magic will happen, and not in the bedroom. Okay, that was bad, but I tried.
Still following? Good.
What good is a Gridview without data? We’ll need to some how give our Gridview data. This can be done anyway that you prefer, whether it be through a DataSource such as a ObjectDataSource, SqlDataSource or AccessDataSource. Or you can call a method from your code behind/beside.
For this example, since I didn’t have any data to use, I manually created a DataSet inside of a method, and called this method in the Page_Load().
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { // GetRestaurants() returns a DataSet of Restaurants. this.gdvRestaurants.DataSource = GetRestaurants(); this.gdvRestaurants.DataBind(); } }
Pretty simple. All that I’m doing is checking if a PostBack caused the Page_Load(), and if not then load the Gridview with the data from the GetRestaurants() method. If it is a PostBack(), then the data will be kept in the viewstate, so we don’t need to fetch it.
If you run your project, you should get something similar to the first image above.
Now onto the “magic” that I spoke of.
Above the Page_Load(), add the following line.
// Used to store the last Category Name between each RowDataBound private string tmpCategoryName = “”;
We will use this variable to check whether the current row’s category is the same as the previous row’s category. If it’s not the same, then we will need to create the sub header row. If it is the same as the previous row, then we can skip creating the sub header.
If we were to declare this variable within the OnRowDatabound, it would always be start off being empty, and therefore we wouldn’t be able to compare it’s value to the current row’s value.
Now to that OnRowDatabound that I keep speaking of.
First, we’ll need to check to see if the current row is a DataRow, and not something else like a HeaderRow. If it is a DataRow, then we will continue doing our stuff to create the sub header row.
To check if it’s a DataRow, add the following to your OnRowDatabound() method.
// If the current row is a DataRow (and not a Header or Footer row), then do stuff. if (e.Row.RowType == DataControlRowType.DataRow) { }
Inside of that, we will need to retrieve the value for the category field of the current row. Do to this we will need to cast the current row as a DataRowView, and then compare it’s “category” field to the value in the tmpCategoryName variable. If the two value are not the same, then we will need to set the value of tmpCategoryName to the new value.
// If the current row is a DataRow (and not a Header or Footer row), then do stuff. if (e.Row.RowType == DataControlRowType.DataRow) { DataRowView drv = (DataRowView)e.Row.DataItem; if (tmpCategoryName != drv["Category"].ToString()) { tmpCategoryName = drv["Category"].ToString(); } }
As I mentioned earlier, the GridView doesn’t have a means of adding a row to it. So what we will do is create a reference to the current row’s Parent, which is a Table object, and happens to be the GridView itself. Once we do that, we will be safe and check to make sure that the reference isn’t null.
// Get a reference to the current row’s Parent, which is the Gridview (which happens to be a table) Table tbl = e.Row.Parent as Table; if (tbl != null) { }
Now we can create the sub header row, and add it to the table (GridView). To do this we will need to create a GridViewRow object which will be the sub header row, and we will need to create a TableCell to place the category name into.
I’ve also applied some styling such as font-weight, background-color and the font color to the cell.
At the end, I’ll add the category name, which is wrapped in a span tag, into the TableCell, and then add the TableCell to the GridViewRow, and then add the GridViewRow to the table.
if (tbl != null) { GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal); TableCell cell = new TableCell(); // Span the row across all of the columns in the Gridview cell.ColumnSpan = this.gdvRestaurants.Columns.Count; cell.Width = Unit.Percentage(100); cell.Style.Add(“font-weight”, “bold”); cell.Style.Add(“background-color”, “#c0c0c0″); cell.Style.Add(“color”, “white”); HtmlGenericControl span = new HtmlGenericControl(“span”); span.InnerHtml = tmpCategoryName; cell.Controls.Add(span); row.Cells.Add(cell); tbl.Rows.AddAt(tbl.Rows.Count - 1, row); }
Now if you were to run the project again, you should have something that looks like the following.

And there we have it.
Just because I’m a nice guy, I’ve decided to include the code as a zip file which can be downloaded here.


Adding (or Inserting) Subheader Rows into a Gridview…
You’ve been kicked (a good thing) - Trackback from DotNetKicks.com…
The code download appears to be incomplete?
OOoops, you’re right. Thanks for pointing that out. Not only is it incomplete, it wasn’t even the correct folder that I zipped.
I’ll upload the correct one shortly.
Thanks again
I’ve re-uploaded the zip, and it’s the correct one this time
Thanks again
Very nice and well presented! This was the simplest solution I found and helped me a great deal. Thanks.
Hey Mike P, no problem, glad I could help.
Thanks for the comments
I have problem witrh the pager.
The cell of the pager set columnspan ever at 1.
thanks for the code
Very Nice - Worked perfectly
Stephen, glad to have been able to help
Hi, This is very good and understanding , if do i want one more sub header , how to do, can u help me carlj.
Thanks a bunch buddy!
I have been struggling to add a row in the grid for the last 10 days.
you made my day!
you are awesome!
thanks once again:)!
No problem, glad I could help.
(:
I’ve got a problem with the paging footer too, with this code the pager’s location is the 1st cell with colspan =1 , instead of being in the center of the table.
Can anybody help me with it?
Hey Txomin,
Do you have the pager inside of the gridview’s footer? Can you send me your code, and a description of what you’re trying to do, to
me@carlj.ca
This worked perfectly for me from a presentation perspective. I think I have an issue though when submitting. I have a Continue button where I want to loop through the rows in the table to get out entered data (some are template columns with text boxes). The count of the rows is thrown off because it doesn’t include the added category rows, just the number of actual records. Is there a workaround for this? For example:
for (int i = 0; i < gvTests.Rows.Count; i++)
{
if (gvTests.Rows[i].RowType == DataControlRowType.DataRow)
{
TextBox txtQty = (TextBox)gvTests.Rows[i].FindControl(”txtQuantity”);
}
}
Hey Jeff, it’s been a while since I did this. Let me try to look at it tonight, and I’ll get back to you. I know that when I originally did this (before posting), I had to go through each row.
What if you did something like the following:
for (int i = 0; i < gvTests.Rows.Count; i++)
{
if(gvTests.Rows[i].RowType == DataControlRowType.DataRow)
{
TextBox txtQty = (TextBox)gvTests.Rows[i].FindControl(”txtQuantity”);
if(txtQty != null)
{
// Do something with txtQty
}
}
}
The only difference, is that you check to see if the txtQty is null or not. When you get to a Sub Header row, the Textbox doesn’t exist, so it’ll be null.
I’ve been playing around a bit more, but still haven’t had any luck. I put two labels on the form and update them with gridview.Rows.Count and tbl.Rows.Count in the RowDataBound event just for testing purposes. The gridview reports 43 rows, and the table 53 when the page is fully loaded (even though I only see 52 rows even in page source). In the button click event I check the counts right away. The gridview count is still 43, but the table count drops to just 45. This is where it’s “broken” because I can’t reference all of the rows.
If I just let the postback run through, the grid redraws without the new rows (which I guess makes sense because they were drawn after the databind which is only done if not a postback in the page load). Where the new rows were originally added, there is no data–that is, the data remained in the original row positions, but some of the data I entered in fields moved up. Kinda messy, and I’m not sure how to work around this yet.
your coding is pretty good and understandable… but i have problem in paging where the paging displays in first cell. moreover i added one link button in gridview column which will redirect to another page.. for the first link its not working and i cant get the id.. help me here is the coding…
<asp:HiddenField ID=”Hidparentid” runat=”server” Value=” />
<asp:LinkButton ID=”LBEdit” Text=” runat=”server” CommandArgument=” OnCommand=”Btnedit_Command” >
<%–
<a id=”A1″ href=”javascript:fnEdit(”);” runat=”server”> <asp:Label runat=”server” ID=”LblName” Text=”>
–%>
<asp:Label ID=”Lblaccount” text=” runat=”server”>
<asp:Label ID=”lblJob” runat=”server” Text=”>
<asp:TextBox ID=”hidAmount” runat=”server” Text=” />
<asp:HiddenField ID=”hidAccType” runat=”server” Value=” />
<asp:TextBox ID=”hidmemo” runat=”server” Text=”>
in .cs file
protected void Btnedit_Command(object sender, CommandEventArgs e)
{
try
{
String IdStr = System.Convert.ToString(e.CommandArgument);
Response.Cookies["purchaseid"].Value = IdStr;
Response.Redirect(”purchases.aspx”);
}
}
@Velmurugan: I’m going out on a limb here, and kind of guessing, since I don’t have the rest of your code.
In Btnedit_Command(), you’re saving the value of e.CommandArgument into IdStr, however, in your aspx page, you’re not setting the value of the CommandArgument property, unless that’s being done in the DataBound.
@JeffD: Have you had any luck? A few weeks ago I was playing around with the GridView, and had a similar problem. The last few weeks have been super busy with Xmas and all, so I haven’t had a chance to look into it some more.
thank u sir… i rectified the error.. thanks for ur coding
@Velmurugan: What did you do to fix it? Was the problem because of the empty CommandArgument?
yes sir its due empty argument
This is a very clever way to add sub-headers to the gridview. However, I ran into a problem. When I click on some button which causing a postback. On postback, it seems the GridView gets rebind to the ViewState without calling calling the RowDataBound event. As a result, the subheaders lost on postback. Is there any fix to this problem?
Thanks
Ron
Hi Carl .Thanks for the solution. Im doing training and inspection forms so that code u provided was a big help.. i just made one change,, instead of referring to the gridview by name u reffrreed it using the sender object.. i rewrote in VB so here
Protected Sub GridView1_DataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs)
‘ this will add a row header every time the itemgroup changes!
If (e.Row.RowType = DataControlRowType.DataRow) Then
Dim drv As DataRowView = CType(e.Row.DataItem, DataRowView)
If (tmpItemGroup drv(”Itemgroup”).ToString) Then
tmpItemGroup = drv(”ItemGroup”)
Dim tbl As Table = CType(e.Row.Parent, Table)
Dim row As GridViewRow = New GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal)
Dim cell As TableCell = New TableCell()
cell.ColumnSpan = sender.Columns.Count
cell.Width = Unit.Percentage(100)
cell.Style.Add(”font-weight”, “bold”)
Dim span As HtmlGenericControl = New HtmlGenericControl(”span”)
span.InnerHtml = tmpItemGroup
cell.Controls.Add(span)
row.Cells.Add(cell)
tbl.Rows.AddAt(tbl.Rows.Count - 1, row)
End If
End If
End Sub
Hello! Thank you for this post. I have used a similar technique to add row spans on cells where data is repeated in a user control I’ve made. However, this control will not render the added sub-header rows.
I have a bit of a complicated structure with a user control containing the grid being added to a ajaxToolkit TabContainer in an other user control. And due to a bug (I believe) in the TabContainer control, I have to databind the grid during the init event of the tabcontainer, and not during the pageload event. I think this would be the reason that the rows are never rendered.
Databinding the gridview on pageload will not work with the tab control. You wouldn’t have a workaround to suggest for this, would you?
Did anyone find a solution to the rebind problem after postback described by JeffD above? I am having that exact problem and I have spent several days trying to figure out a way around this. Any body have any ideas, Help!!
Carl, Phil, JeffD, anyone: Has anyone solved the rebind problem?? I need a fix ASAP. I’ve been at this for 12 hours straight. PLEASE let me know if you know of a work-around for this.
Thanks!
Hi,
I have used the above code and facing some problem in data retrieval.I am getting wrong values from the rows.Could you please send me the full code with retrieval .
hi i am using the above code, working well. I am using LinkButton in the gridview through this i will popup a window. My problem is the links in the bottom n number is not working. where n is the number of sub headings in the gridview. Please help me to solve this.
Thanks in advance.
Regards,
Sathieshkumar .R
I found the solution for pager problem, and somebody else can explain why is it happening (I don’t know). Anyway instead of
GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
just define as
GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
and that should do the trick
The solution for the rebind problem after postback, and for the loss of the e.CommandArgument content in the LinkButton, is : set EnableViewState=”False” for the GridView. It worked for me.
hello sir ur code is very good 4 filling gridview bt i hv textboxes in gv 2 insert data in database bt it is not retreiving correct rows from gridview plz tell me way to solve this problem
itz urgent
reply will b highly appreciated