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.

So here is my solution.  I'll cover the main points of interest and if you want to see more, I have
uploaded all the source and example to the new MSDN Code Gallery.
http://code.msdn.microsoft.com/AlwaysShowHeaderFoot

Start out by extending the GridView control and add the following structure. 
The most important part here is to override the PerformDataBinding method.
 ClassStructure

As you can see here.  I am intercepting the data and making sure there is at lease one row. 
If there is, the control behaves normally.
On the line with the red arrow, I am checking if the binding source is a DataView. 
If it is, I can just add a row here and be done with it.
If the binding source is not a DataView (or DataSet), then I fire an event that will need to be handled.
 image

The event "MustAddARow", as seen here, will provide access to the binding data and allow you to add a row.
 image

Here is a snippet from a page where I handle the MustAddARow event.
In this case, I am binding a List of Products to the GridView.
As you can see, I'm just adding a new Product to the list.  It doesn't matter what data you add here
because it will get hidden in back in the GridView.
image

And finally back in the GridView, I override the OnDataBound method so I can hide that extra row.
image 

So there you have it.  This is my first blog post ever. 
Hopefully someone will get some use out of this.

Thanks
-Joe

23 Comments

  • Great solution, as you say, this is the first solution I've seen that does not feel "dirty"

  • You could also put your header and footer in the EmptyDataTemplate. It isn't DRY, but it gets the job done without any code.

  • A neat solution, thanks.

    Don't be scared -- posts that people can use in real-world solutions make a blog useful by definition.








  • <asp:Label ID="Label1" runat="server" Text=''>






    If I changed the bind field is bind expression :
    When the datasoure is null, the result is thorw a System.InvalidCastException exception



  • How can i solve this issue? In this situation,if the datasoure record is empty , .net 1.1 DataGrid display is true.

    although we can changed the expression is :


    But i think it's not a good idea....

  • Pretty neat and clean solution. Kudos.

    Just wondering:
    When checking the the data's type, why aren't you simply using "if (data is DataView)"?

  • i decided to just set the controle to a state of edit and enabled = flase when the session vareable that i based my qry on is = nothing

    this is an example of a details view


    If Session("OrderID") = Nothing Then
    dvTruckvsOrderInfo.ChangeMode(DetailsViewMode.Insert)
    dvTruckvsOrderInfo.Enabled = False
    dvTopRoghtOnRight.ChangeMode(DetailsViewMode.Insert)
    dvTopRoghtOnRight.Enabled = False
    Else
    dvTruckvsOrderInfo.ChangeMode(DetailsViewMode.ReadOnly)
    dvTruckvsOrderInfo.Enabled = True
    dvTopRoghtOnRight.ChangeMode(DetailsViewMode.ReadOnly)
    dvTopRoghtOnRight.Enabled = True
    End If

  • Is there any particular reason why it does not respect the SkinId setting as per the normal GridView?

  • Hi Joe,

    I have issue when use this class
    I can run it under .NET 3.5 Framework.
    However, due to server limitation, we need to downgrade to .NET 2.0 and error comes:

    Callbacks are not supported on TemplateField because some controls cannot update properly in a callback. Turn callbacks off on GridViewAlwaysShow.

    After I search through in Google, I found that Please read MSDN.

    http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.enablesortingandpagingcallbacks.aspx

    All columns in the Columns collection must support callbacks for this feature to work. If the Columns collection contains a column that does not support callbacks, such as TemplateField, a NotSupportedException exception is raised.

    When I turn off all Sorting and SortExpression, the issue is resolved in 2.0.

    Prehaps you may add a hotfix to override ValidateSupportsCallback for each templateField or added a known issue as reminder.

    Anyway, great job for this and look fwd to seeing more.

    Cheers,
    Billy







  • Hi,

    If a dropdownlist is included in the gridview, it will cause a "SelectedValue which is invalid because it does not exist in the list of items" error when attempting to PerformDataBinding(dv.Table.DefaulView).

    How would you deal with this?
    TIA
    Neal

  • Hi again,

    Found that if I add "UNION SELECT NULL AS ID, NULL as DropDownID, etc" to my sqldatasource selectcommand, I no longer incur this error.

    Thanks for your help.
    Neal

  • Not knowing enough to have the objections to the alternatives you found, I took a different approach, using the EmptyDataTemplate.

    I recreated the (static) header and the (active) footer, with the controls (DropDownLists and TextBoxes) in the footer cells given the same ID as the templated footer controls. The .aspx ID is the basename for what's rendered into HTML, and as of .NET 2.0, the framework is OK with that pseudo-overloading. That permits the same codebehind approach, at least, in handling user inputs from the footer and pseudo-footer.

  • I got the same thing done more easier way

    DataTable dt = new DataTable();
    dt = ContentReportDALC.GetContentReportDetailsForGivenDate(strSelectedDate);
    if (dt.Rows.Count == 0)
    {
    lblErrorMessage.Text = "There is no data present with the given date, Please Modify";

    dt.Rows.Add(0,string.Empty, 0, 0, 0, 0, 0, 0, 0); // you need to follow the data source schema here...
    GridView1.DataSource = dt;
    GridView1.DataBind();


    }
    else
    {
    GridView1.DataSource = dt;
    GridView1.DataBind();
    }

  • Why not intercept things on the onRowDataBound and go from there? I just played with this bit of code and it worked like a charm...

    Dim TargetGridView = CType(sender, GridView)
    Dim TargetRowIndex As Integer = e.Row.RowIndex

    If TargetGridView.Rows.Count = 0 Then
    Dim TargetTable As Table = DirectCast(TargetGridView.Controls(0), Table)
    Dim NewTableCell As New TableCell
    Dim NewGridViewRow As New GridViewRow(0, 0, DataControlRowType.Header, DataControlRowState.Normal)

    Dim NewTableCellText As New StringBuilder

    NewTableCellText.Append("")
    NewTableCellText.Append("")
    NewTableCellText.Append("Test")
    NewTableCellText.Append("")
    NewTableCellText.Append("")
    NewTableCellText.Append("Test")
    NewTableCellText.Append("")
    NewTableCellText.Append("")
    NewTableCell.Text = NewTableCellText.ToString


    NewGridViewRow.Cells.Add(NewTableCell)
    NewTableCell.ColumnSpan = TargetGridView.Columns.Count
    TargetTable.Controls.AddAt(0, NewGridViewRow)
    End If

  • ...that snippet works if the EmptyDataText is set. I'm curious now about getting the GridView's Header and Footer template and then adding them via code.

  • ...turns out i posted a bit too fast...

  • This is really a common problem. Hard to remember where, but I guess I read somewhere that hiding header and footer when data is present must be a bug (!) for the gridview control. This seems to be bit logical, since some unnecessary coding has to be done [a very simple approach, you can see here: http://dotnetspidor.blogspot.com/2008/07/how-to-display-gridview-header-and.html] to show the header and footer again.

    Anyway, there now exist a number of solutions. That is good.

  • Sorry for the verbal mistake: [rewritten]
    Hard to remember where, but I guess I read somewhere that hiding header and footer when data is ***not*** present must be a bug (!) for the gridview control.

  • + Work without problem
    + Elegant solution

    Perfect!

  • What's the VB equivalent of line 76 (public event MustAddARowHandler MustAddARow;)?

  • New in .NET 4.0: GridView.ShowHeaderWhenEmpty (boolean)

    http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.showheaderwhenempty.aspx

  • Hello can you tell me what is List? I know it is a List but IT DON?T WORK IT.... Where I put this code?:

    protected IEnumerable GrindView_MustAddRow(IEnumerable data){
    List dds = (List)data;
    // dds.Add(new Product());
    // return dds;

    }

    Thanks a lot.... I have been trying use it since I saw it. But I can't.
    harrycomnet@hotmail.com

  • ya.. i m getting the same error as harry... can anyone help plz......
    protected IEnumerable GridView1_MustAddARow(IEnumerable data)
    {
    List dds = (List)data;
    dds.Add(new Product());
    return dds;

    }
    here error is product could not be found......
    so plz.... help me for this.........

Comments have been disabled for this content.