Search Wiki:
Resource Page Description
A more elegant solution to display GridView header and footer when the data source is empty.

I think the need to always show the header and footer of a GridView is pretty common.
When I first ran into this problem, I went to Google and found lots of content about this.
Some suggest tampering with the data source to add an extra row if it’s empty. Others show overriding the CreateChildControls method.

I was not satisfied with either of these solutions. I didn’t like that dirty feeling I had by tampering with the data source. And I didn’t like overriding the CreateChildControls method because it simply didn’t work of me anyway. This solution only gave the appearance of the header and footer existing. I ran into issues because I was programmatically adding controls to the header. Upon postback, if the data source was empty, the control hierarchy would not be the same as before thus causing an error.


I blogged about this here
http://weblogs.asp.net/joewrobel/archive/2008/01/30/a-more-elegant-solution-to-display-gridview-header-and-footer-when-the-data-source-is-empty.aspx
Last edited Jan 31 2008 at 3:52 AM  by robolize, version 3
Comments
Boretskiy wrote  Feb 14 2008 at 7:50 PM  
Thanks a lot for you post it was helpful me. But I added to your variant some changes for correct raising event and registration controls.
Below you can see full version.

private GridViewRow _headerRow;
private GridViewRow _footerRow;

[Category("Behavior")]
[Themeable(true)]
[Bindable(BindableSupport.No)]
public bool ShowHeaderWhenEmpty
{
get { return _showHeaderWhenEmpty; }
set { _showHeaderWhenEmpty = value; }
}

[Category("Behavior")]
[Themeable(true)]
[Bindable(BindableSupport.No)]
public bool ShowFooterWhenEmpty
{
get { return _showFooterWhenEmpty; }
set { _showFooterWhenEmpty = value; }
}

public override GridViewRow HeaderRow
{
get { return base.HeaderRow ?? _headerRow; }
}

public override GridViewRow FooterRow
{
get { return base.FooterRow ?? _footerRow; }
}

protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int rows = base.CreateChildControls(dataSource, dataBinding);

// no data rows created, create empty table if enabled
if (rows == 0 && (ShowFooterWhenEmpty || ShowHeaderWhenEmpty))
{
// create the table
Table table = CreateChildTable();

Controls.Clear();
Controls.Add(table);

DataControlField[] fields;
if (AutoGenerateColumns)
{
PagedDataSource source = new PagedDataSource();
source.DataSource = dataSource;

ICollection autoGeneratedColumns = CreateColumns(source, true);
fields = new DataControlField[autoGeneratedColumns.Count];
autoGeneratedColumns.CopyTo(fields, 0);
}
else
{
fields = new DataControlField[Columns.Count];
Columns.CopyTo(fields, 0);
}

TableRowCollection newRows = table.Rows;
if (ShowHeaderWhenEmpty)
{
// create a new header row
_headerRow = CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
InitializeRow(_headerRow, fields, newRows);
}

// create the empty row
GridViewRow emptyRow = new GridViewRow(-1, -1, DataControlRowType.EmptyDataRow, DataControlRowState.Normal);
TableCell cell = new TableCell();
cell.ColumnSpan = fields.Length;
cell.Width = Unit.Percentage(100);

// respect the precedence order if both EmptyDataTemplate
// and EmptyDataText are both supplied ...
if (EmptyDataTemplate != null)
{
EmptyDataTemplate.InstantiateIn(cell);
}
else if (!string.IsNullOrEmpty(EmptyDataText))
{
cell.Controls.Add(new LiteralControl(EmptyDataText));
}

emptyRow.Cells.Add(cell);
GridViewRowEventArgs e = new GridViewRowEventArgs(emptyRow);
OnRowCreated(e);

newRows.Add(emptyRow);

emptyRow.DataBind();
OnRowDataBound(e);
emptyRow.DataItem = null;

if (ShowFooterWhenEmpty)
{
// create footer row
_footerRow = CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);
InitializeRow(_footerRow, fields, newRows);
}

}

return rows;
}

private void InitializeRow(GridViewRow row, DataControlField[] fields, TableRowCollection newRows)
{
GridViewRowEventArgs e = new GridViewRowEventArgs(row);
InitializeRow(row, fields);
OnRowCreated(e);

newRows.Add(row);

row.DataBind();
OnRowDataBound(e);
row.DataItem = null;
}

rkaltenstein wrote  Mar 17 2008 at 1:56 AM  
This was a big help. Thanks. Noticed your comment about counting the data items. Since you only ever test whether there's more than one, you could just see whether you can move to a fist one and then reset the enumerator:

bool anyItems = data.GetEnumerator().MoveNext();
data.GetEnumerator().Reset();

projammer wrote  Jun 28 2008 at 7:08 PM  
Why not just add a table in. I have text boxes for users to add things at the bottom of the would be table. So instead of going through all of this, I used the EmptyDataTemplate. I did not name anything since there is no need in calling it. Here is the code.

<EmptyDataTemplate>
<asp:Table ID="tblEmptyHeadCount" runat="server" Width="100%">
<asp:TableRow ID="TableRow17" runat="server" HorizontalAlign="right" Width="100%">
<asp:TableCell ID="TableCell42" runat="server" Width="15%"></asp:TableCell>
<asp:TableCell ID="TableCell43" runat="server" Width="40%" HorizontalAlign="center">
<asp:Label ID="lbl1" runat="server" Text="Position"></asp:Label>
</asp:TableCell>
<asp:TableCell ID="TableCell48" runat="server" Width="15%" HorizontalAlign="center">
<asp:Label ID="Label6" runat="server" Text="FTE Salary"></asp:Label>
</asp:TableCell>
<asp:TableCell ID="TableCell49" runat="server" Width="10%" HorizontalAlign="center">
<asp:Label ID="Label7" runat="server" Text="HC"></asp:Label>
</asp:TableCell>
<asp:TableCell ID="TableCell50" runat="server" Width="20%" HorizontalAlign="center">
</asp:TableCell>
</asp:TableRow>
</asp:Table>

</EmptyDataTemplate>

jeremylei wrote  Jul 3 2008 at 4:35 AM  
Hi There.
Just thought I should add some code for you as your example will not work with the standard .NET datatables that contain non-null constraints etc. This code is within the 'PerformDataBinding' Override.

//If it's a DataView, it will work without having to handle the MustAddARow event.
if (data.GetType() == typeof(DataView))
{
DataView dv = (DataView)data;
//Create a table with the same schema as data, but with no constraints
DataTable dt = new DataTable(dv.Table.TableName);
//copy columns
foreach(DataColumn dc in dv.Table.Columns)
{
dt.Columns.Add(dc.ColumnName);
}
//Add a row and use that new view.
dt.Rows.Add(dt.NewRow());
dv = new DataView(dt);
base.PerformDataBinding(dv.Table.DefaultView);
return;
}
else
{
//If you are using some custom object, you need to handle this event.
base.PerformDataBinding(OnMustAddARow(data));
return;
}

Echilon wrote  Jan 23 2009 at 11:53 AM  
Cheers, without knowing I had non null constraints.

SSY wrote  Sep 16 2009 at 12:08 PM  
Hi,
I am binding the gridview to a generic list. How can I show header and footer when there is no data.

Thanks a lot in advance!
SSY

WaleedMohamed wrote  Oct 8 2009 at 11:07 PM  
please check this post also hope be useful in this issue
http://ledomoon.blogspot.com/2009/04/show-grid-view-header-and-footer-when.html

Smitty62 wrote  Mar 26 2010 at 8:34 PM  
I'm new to .net and am using Visual Studio 2008. VS2008 does allow "MustAddARow" in the <asp:Gridview > clause. Do you know what the equivalent would be for VS2008?

Smitty62 wrote  Mar 26 2010 at 8:35 PM  
It also does not allow <EmptyDataTemplate>

wirestealth wrote  Oct 11 2011 at 5:22 AM  
I realize this is over 1.5 years old but this is still the best/most elegant solution to the problem so I thought I would just add a touch more to it.
Yes this implementation prevents the use of <EmptyDataTemplate> for (data not empty but contains a single hidden row) but it's easy enough to customize it to just use that row for your own message in OnDataBound:
if (_isEmpty)
{
//Rows[0].Visible = false;
// Hide all the conotrols.
foreach (Control ctrl in Rows[0].Controls)
{
ctrl.Visible = false;
}
// Now add a control and use the user-provided string in the EmptyDataText property if supplied, otherwise use a default.
TableCell cell = new TableCell();
cell.ColumnSpan = this.Columns.Count;
if (this.EmptyDataText.Length > 0)
{
cell.Text = EmptyDataText;
}
else
{
cell.Text = String.Format("No records were found using filter \"{0}\".", _filterString);
}

Rows[0].Controls.Add(cell);

}
- Making all the controls visible=false will effectively hide the row as well but then you can add whatever you like into that row as required...I chose to use a single cell spanning all the columns and further use the EmptyDataText property of the GridView if provided (or a custom one reflecting the filters they chose in my grid).
- Joe's solution + the non-null constraint patch has worked out great! Thanks!

Thanks again Joe for providing a great solution!

Updating...
Page view tracker