How do I get the GridView
control to render the <thead>
<tbody>
tags? I know .UseAccessibleHeaders
makes it put <th>
instead of <td>
, but I cant get the <thead>
to appear.

- 57,289
- 29
- 176
- 237

- 36,616
- 34
- 155
- 231
-
FYI: UseAccessibleHeader is "true" by default, so you don't need to set it. http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.useaccessibleheader.aspx – MikeTeeVee Nov 07 '13 at 20:14
8 Answers
This should do it:
gv.HeaderRow.TableSection = TableRowSection.TableHeader;

- 4,396
- 1
- 27
- 22
-
72The `HeaderRow` property will be `null` until the `GridView` has been data bound, so make sure to wait until databinding has occurred before running the above line of code. – bdukes Jul 17 '09 at 14:47
-
8As comment below, with ASP.NET 4.5 at least after binding isn't late enough - it works in OnPreRender however. – philw Aug 06 '13 at 11:16
-
I have a gridview with custom sub headers added. Each of these sub headers do show data from the data source. The reason I wanted to render `thead` is to use it in jQuery. However after rendering header, the `tbody` doesn't seem to be available. What may be missing in my case? – bonCodigo Sep 03 '14 at 08:54
-
1I found there was still problems during postback and placed the code in the databound event which addressed all scenarios. – James Westgate Jun 10 '15 at 08:12
-
I bring my data from a database when user clicks on a button. In that case the gridview is missing the thead tag. Any help? – touinta Jul 21 '15 at 09:16
-
-
Add the ShowHeaderWhenEmpty="True" attribute to the GridView control if you want to render a grid with no rows. – cymorg Jun 20 '18 at 23:30
I use this in OnRowDataBound
event:
protected void GridViewResults_OnRowDataBound(object sender, GridViewRowEventArgs e) {
if (e.Row.RowType == DataControlRowType.Header) {
e.Row.TableSection = TableRowSection.TableHeader;
}
}
-
8This is the only solution that worked for me. Who designed these terrible controls? – EKW Aug 03 '16 at 20:52
-
2I inserted your code into the OnRowCreated event and got it to work correctly. – yougotiger Mar 08 '18 at 23:55
-
This is the best solution because it removes the risk (and required check) of the TableSection being null if no rows are within the DataSource. – EvilDr Nov 08 '18 at 11:59
-
1Fyi, if the `GridView` is within an `UpdatePanel` and an async-postback is caused by some other control then the `OnRowDataBound` event won't be raised thus the code in this answer won't be executed, resulting in the `GridView` reverting to rendering without `` tags... _sigh_. To target this case, shove the code from [the accepted answer](https://stackoverflow.com/a/309119/1204599) into the gridView's `PreRender` event handler (much like [ASalvo's answer](https://stackoverflow.com/a/808819/1204599) suggests). – Mr.Z Dec 19 '18 at 17:49
-
The code in the answer needs to go on Page_Load
or GridView_PreRender
. I put it in a method that was called after Page_Load
and got a NullReferenceException
.
-
4You can also put in `DataBound` event. `grid.DataBound += (s, e) => { grid.HeaderRow.TableSection = TableRowSection.TableHeader; };` – BrunoLM Aug 12 '10 at 11:53
-
4Don't know if this is different in .NET 4.5 now... but I am getting the HeaderRow being null in both _DataBound and _PreRender event handlers. This might be related to the fact I am using ASP.NET Web Forms new "Model Binding" feature in the gridView. – Jason Parker Oct 19 '12 at 18:53
I use the following code to do this:
The if
statements I added are important.
Otherwise (depending on how you render your grid) you'll throw exceptions like:
The table must contain row sections in order of header, body and then footer.
protected override void OnPreRender(EventArgs e)
{
if ( (this.ShowHeader == true && this.Rows.Count > 0)
|| (this.ShowHeaderWhenEmpty == true))
{
//Force GridView to use <thead> instead of <tbody> - 11/03/2013 - MCR.
this.HeaderRow.TableSection = TableRowSection.TableHeader;
}
if (this.ShowFooter == true && this.Rows.Count > 0)
{
//Force GridView to use <tfoot> instead of <tbody> - 11/03/2013 - MCR.
this.FooterRow.TableSection = TableRowSection.TableFooter;
}
base.OnPreRender(e);
}
The this
object is my GridView.
I actually overrode the Asp.net GridView to make my own custom control, but you could paste this into your aspx.cs page and reference the GridView by name instead of using the custom-gridview approach.
FYI: I haven't tested the footer logic, but I do know this works for Headers.

- 18,543
- 7
- 76
- 70
This works for me:
protected void GrdPagosRowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
e.Row.TableSection = TableRowSection.TableBody;
}
else if (e.Row.RowType == DataControlRowType.Header)
{
e.Row.TableSection = TableRowSection.TableHeader;
}
else if (e.Row.RowType == DataControlRowType.Footer)
{
e.Row.TableSection = TableRowSection.TableFooter;
}
}
This was tried in VS2010.

- 1,690
- 1
- 23
- 24

- 51
- 1
- 1
I know this is old, but, here's an interpretation of MikeTeeVee's answer, for a standard gridview:
aspx page:
<asp:GridView ID="GridView1" runat="server"
OnPreRender="GridView_PreRender">
aspx.cs:
protected void GridView_PreRender(object sender, EventArgs e)
{
GridView gv = (GridView)sender;
if ((gv.ShowHeader == true && gv.Rows.Count > 0)
|| (gv.ShowHeaderWhenEmpty == true))
{
//Force GridView to use <thead> instead of <tbody> - 11/03/2013 - MCR.
gv.HeaderRow.TableSection = TableRowSection.TableHeader;
}
if (gv.ShowFooter == true && gv.Rows.Count > 0)
{
//Force GridView to use <tfoot> instead of <tbody> - 11/03/2013 - MCR.
gv.FooterRow.TableSection = TableRowSection.TableFooter;
}
}

- 403
- 6
- 10
Create a function and use that function in your PageLoad
event like this:
The function is:
private void MakeGridViewPrinterFriendly(GridView gridView) {
if (gridView.Rows.Count > 0) {
gridView.UseAccessibleHeader = true;
gridView.HeaderRow.TableSection = TableRowSection.TableHeader;
}
}
The PageLoad
event is:
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack)
{
MakeGridViewPrinterFriendly(grddata);
}
}

- 1,951
- 2
- 16
- 19
You can also use jQuery to add it. This avoids the problem with TableRowSection.TableHeader where gets dropped on PostBack.
$('#myTableId').prepend($("<thead></thead>").append($(this).find("#myTableId tr:first")));

- 2,825
- 3
- 24
- 30