How To: Show Header and Footer Rows in an Empty GridView
If you have worked with the ASP.NET 2.0 GridView control, I am sure you were more than a little surprised when you realized there is no decent way to display the Header or Footer rows of a GridView that is bound to an empty DataSource. I know I was. It seems like a bug to me and I am surprised it made it through usability testing. Anyway, there are basically 2 ways to work around this problem:
- Make your DataSource's return a special empty row so the grid has something to bind to (see example of this here)
- Subclass GridView and add this functionality yourself
I chose to go with option 2 because because we had already subclassed the GridView for other reasons. I cheated a little bit and started with the code snippet on the following page. The GridView control is very complicated and I am not going to claim I understand it entirely, but from what I could tell this starting code segment contains the following bugs:
- It doesn't work when you have AutoGenerateColumns set to true
- It doesn't respect the EmptyDataTemplate (it only accounts for the EmptyDataText)
- It doesn't render the Footer row
So I fixed these bugs and figured I would pass on the resulting code.
You can view a live demo of this in action here, and below is a screen shot of a page using this feature along with all of the code. Enjoy!
Screen Shot:
Markup:
<mb:GridView runat="server" DataSourceID="sqldsCustomers" ShowHeader="true" ShowHeaderWhenEmpty="true" EmptyDataText="No Customers were found." Width="95%" />
Code:
/// <summary> /// /// </summary> /// <param name="dataSource"></param> /// <param name="dataBinding"></param> /// <returns></returns> protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding) { int rows = base.CreateChildControls(dataSource, dataBinding); // no data rows created, create empty table if enabled if (rows == 0 && (this.ShowFooterWhenEmpty || this.ShowHeaderWhenEmpty)) { // create the table Table table = this.CreateChildTable(); DataControlField[] fields; if (this.AutoGenerateColumns) { PagedDataSource source = new PagedDataSource(); source.DataSource = dataSource; System.Collections.ICollection autoGeneratedColumns = this.CreateColumns(source, true); fields = new DataControlField[autoGeneratedColumns.Count]; autoGeneratedColumns.CopyTo(fields, 0); } else { fields = new DataControlField[this.Columns.Count]; this.Columns.CopyTo(fields, 0); } if (this.ShowHeaderWhenEmpty) { // create a new header row GridViewRow headerRow = base.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal); this.InitializeRow(headerRow, fields); // add the header row to the table table.Rows.Add(headerRow); } // 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 (this.EmptyDataTemplate != null) { this.EmptyDataTemplate.InstantiateIn(cell); } else if (!string.IsNullOrEmpty(this.EmptyDataText)) { cell.Controls.Add(new LiteralControl(EmptyDataText)); } emptyRow.Cells.Add(cell); table.Rows.Add(emptyRow); if (this.ShowFooterWhenEmpty) { // create footer row GridViewRow footerRow = base.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal); this.InitializeRow(footerRow, fields); // add the footer to the table table.Rows.Add(footerRow); } this.Controls.Clear(); this.Controls.Add(table); } return rows; } [Category("Behavior")] [Themeable(true)] [Bindable(BindableSupport.No)] public bool ShowHeaderWhenEmpty { get { if (this.ViewState["ShowHeaderWhenEmpty"] == null) { this.ViewState["ShowHeaderWhenEmpty"] = false; } return (bool)this.ViewState["ShowHeaderWhenEmpty"]; } set { this.ViewState["ShowHeaderWhenEmpty"] = value; } } [Category("Behavior")] [Themeable(true)] [Bindable(BindableSupport.No)] public bool ShowFooterWhenEmpty { get { if (this.ViewState["ShowFooterWhenEmpty"] == null) { this.ViewState["ShowFooterWhenEmpty"] = false; } return (bool)this.ViewState["ShowFooterWhenEmpty"]; } set { this.ViewState["ShowFooterWhenEmpty"] = value; } }
Comments
Work like a charm! Thank you!
This is awesome! You have saved me literally hours of work. Much thanks!
Anyway to get a binary of this class along with the appropriate "registration" line for the .aspx?
Thanks for a great post, Matt. I see your markup for putting the control on the page, but I'm unsure how you registered the control, or are able to reference the class from your .aspx page? Thanks again.
The only problem is that the FooterRow object is null when referenced from the GridView, so it is impossible to use this solution for e.g. inserting a new record from the footer. Would be really great if you could solve this!
A solution to the problem that GridView.FooterRow property is null when the DS is empty is easially solved by overide the FooterRow property
protected GridViewRow _footerRow = null;
public override GridViewRow FooterRow
{
get { return _footerRow == null ? base.FooterRow : _footerRow; }
}
and replacing
GridViewRow footerRow = base.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);
with
_footerRow = base.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);
BR Anders
I try this last solution for inserting a new record but my ADD button in FooterRow not fire the RowCommand event from GridView. I don't know why?
Hi,
This looks exactly what I need, but I second Ken Cherasaro's comment - how do you do the subclassing, then access the empty gridview from the page? What does the
Thanks,
Stew
Anders' comment was great, but if you use a bound listcontrol (dropdown list for instance) in the footer, you will get exception.
Forget my previous comment. That was caused by the EVIL copypaste...
great code, Thank you!
It's very useful code!
But you forgot
base.OnRowCreated(new GridViewRowEventArgs(_footerRow));
For some reason code:
rows = base.CreateChildControls(dataSource, dataBinding);
throws an exception if the datasource have not records.
I propose:
int rows = 0; System.Collections.IEnumerator en = dataSource.GetEnumerator();
en.Reset();
if (en.MoveNext())
rows = base.CreateChildControls(dataSource, dataBinding);
Before seeing this article i don't have any clue of this problem , Its Really Helpfull to me
very helpfull, thank you!
Thank you this worked beautifully!
It worked very well for me. Thanks.
Thanks a lot for this, it works well. I have a small issue that I can't seem to get around though.
Using this control, when there is no data, the DataBound event does not fire-- I need the DataBound event to fire, but I'm not sure about this control's lifecycle. Obviously there is a call not being made because there is no data, but how can I force DataBound to fire? I've tried manually calling the event during the EnsureDataBound method, which sort of works, but none of the rows are created at that point in the lifecycle, so that won't work. I need the event to fire at the same time it would if there were data, or after that time, but not before.
Can anyone help with that?
Really very useful, but if u have for instance in FooterTemplate control like ImageButton with CausesValidation property set to false and when click nothing happened, just interesting why
It would have been nice to be able to download a working project but nevertheless, it's the best solution of that kind to my knowledge. Thank you...
I really like your code but would love to be able to see all of the code. The link you provided when viewing the actual GridView to download the code does not work, can you please fix this so we can see the code. Thanks.