Monday, September 22, 2008

Using GridView Footer to Insert Record even if Empty

(Updated 10/29 to include ASP.Net example

A great thanks to Matt Berseth for his post on subclassing the GridView to show footers even when the GridView has no records. I used a C# to VB.Net converter to convert his excellent example and the net result is the code below (and includes the tweak/fix for persisting the Footer on PostBack). A few pecularities I noticed about using the Footer.

  • I could not use two-way binding with my ObjectDataSource in the footer so I used the traditional code-behind approach with FindControl (insert does not use the DataSource at all)
  • to find your controls you have to use MyGridView.Footer.FindControl(“aTextBox”)
  • to hide/show the footer I added buttons to the header and footer that fire the GridView.RowCommand (note, the Insert also uses this technique)

Public Class MyGridView
Inherits GridView

Protected Overloads Overrides Function CreateChildControls(ByVal dataSource As System.Collections.IEnumerable, ByVal dataBinding As Boolean) As Integer
Dim numRows As Integer = MyBase.CreateChildControls(dataSource, dataBinding)

'no data rows created, create empty table if enabled
If numRows = 0 AndAlso ShowWhenEmpty Then
'create table
Dim table As New Table()
table.ID = Me.ID

'convert the exisiting columns into an array and initialize
Dim fields As DataControlField() = New DataControlField(Me.Columns.Count - 1) {}
Me.Columns.CopyTo(fields, 0)

If Me.ShowHeader Then
'create a new header row
Dim headerRow As GridViewRow = MyBase.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal)

Me.InitializeRow(headerRow, fields)
table.Rows.Add(headerRow)
End If

'create the empty row
Dim emptyRow As New GridViewRow(-1, -1, DataControlRowType.EmptyDataRow, DataControlRowState.Normal)

Dim cell As New TableCell()
cell.ColumnSpan = Me.Columns.Count
cell.Width = Unit.Percentage(100)
If Not [String].IsNullOrEmpty(EmptyDataText) Then
cell.Controls.Add(New LiteralControl(EmptyDataText))
End If

If Me.EmptyDataTemplate IsNot Nothing Then
EmptyDataTemplate.InstantiateIn(cell)
End If

emptyRow.Cells.Add(cell)
table.Rows.Add(emptyRow)

If Me.ShowFooter Then
'create footer row
'Dim footerRow As GridViewRow = MyBase.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal)
_footerRow = MyBase.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal)

Me.InitializeRow(footerRow, fields)
table.Rows.Add(footerRow)
End If

Me.Controls.Clear()
Me.Controls.Add(table)
End If

Return numRows
End Function


<Category("Behaviour")> _
<Themeable(True)> _
<Bindable(BindableSupport.No)> _
Public Property ShowWhenEmpty() As Boolean
Get
If ViewState("ShowWhenEmpty") Is Nothing Then
ViewState("ShowWhenEmpty") = False
End If

Return CBool(ViewState("ShowWhenEmpty"))
End Get
Set(ByVal value As Boolean)
ViewState("ShowWhenEmpty") = value
End Set
End Property

Protected _footerRow As GridViewRow = Nothing
Public Overloads Overrides ReadOnly Property FooterRow() As GridViewRow
Get
Return IIf(_footerRow Is Nothing, MyBase.FooterRow, _footerRow)
End Get
End Property


End Class



<asp:ValidationSummary ID="vsumDocErrors" runat="server" />
<cc1:MyGridView ID="gvwDocs" runat="server" AutoGenerateColumns="False" DataSourceID="dsDocs"
DataKeyNames="ID" ShowWhenEmpty="true">
<EmptyDataTemplate>
No related data elements</EmptyDataTemplate>
<Columns>
<asp:TemplateField>
<headertemplate>
<asp:ImageButton runat="server" CommandName="Add" AlternateText="Add New Item" ImageUrl="../images/add.png"
OnPreRender="AddButton_PreRender"/>
</headertemplate>
<itemtemplate>
<asp:ImageButton runat="server" CommandName="Select" AlternateText="View Item" ImageUrl="../images/MGlass.png" />
<asp:ImageButton ID="btnDelDoc" runat="server" CommandName="Delete" AlternateText="Delete Item"
ImageUrl="../images/cross.png"
visible="<%# (UserCanEdit) AndAlso (gvwDocs.ShowFooter = False) %>"
OnClientClick='<%# DataBinder.Eval(Container.DataItem, "Description", "return confirm(""Are you sure you want to delete document: [{0}] ?"");") %>' />
</itemtemplate>
<footertemplate>
<asp:ImageButton ID="btnFooterCancel" runat="server" CommandName="CancelAdd" AlternateText="Cancel Add" CausesValidation="false"
ImageUrl="../images/Cancel.png"
OnClientClick="doCheck = false;" />
<asp:ImageButton ID="ImageButton1" runat="server" CommandName="Insert" CommandArguement="Insert" AlternateText="Save Item"
ImageUrl="../images/disk.png"
OnClientClick="doCheck = false;" />
</footertemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Document Name">
<itemtemplate>
<asp:Label runat="server" Text='<%# CType(Container.DataItem, MyProj.Lib.Document).Description %>'></asp:Label>
</itemtemplate>
<footertemplate>
<div><asp:Label runat="server" AssociatedControlID="inDocument" CssClass="Label" Style="width:120px">Select File</asp:Label>
<asp:RequiredFieldValidator runat="server" Display="Dynamic" ErrorMessage="File has not been selected" controlToValidate="inDocument">*</asp:RequiredFieldValidator></div>
<asp:FileUpload runat="server" id="inDocument" Style="width:500px;font-size:x-small;" />
<asp:RegularExpressionValidator id="vldDocument" runat="server" OnLoad="Setup_Doc_Validator" ControlToValidate="inDocument" />
<div><asp:Label runat="server" AssociatedControlID="inDocName" CssClass="Label" Style="width:120px">Friendly Name</asp:Label>
<asp:TextBox runat="server" id="inDocName" Columns="40" Style="font-size:x-small" />(optional)</div>

</footertemplate>
</asp:TemplateField>
<asp:BoundField DataField="CreatedBy" HeaderText="Created By" />
</Columns>
</cc1:MyGridView>
<asp:ObjectDataSource ID="dsDocs" runat="server" SelectMethod="GetDocumentsByAgency"
TypeName="MyProj.Lib.DocumentDAL" DeleteMethod="Delete" DataObjectTypeName="MyProj.Lib.Document">
<SelectParameters>
<asp:QueryStringParameter Name="intAgencyID" QueryStringField="PID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>


7 comments:

Anonymous said...

Nice article. How would you bind a list of values to a dropdownlist in the empty row?

ChrisS said...

Thanks! I couldn't post the code in the comment, so I created a new post. See Binding in Footer

Anonymous said...

supper difficult.
I made more easy ..

Simply on the query, add a blank row (UNION SELECT 0, .. tipically where 0 is the key).

This will put the blank row on, now you only need to make invisible
onDatabound,
Using this way, you will ever have a Footer row available to put on the insert controls.

Alejandro Barrada Martín
Asp.Net Blog

Eng.Waleed said...

please check this post also as a solution for this issue
http://ledomoon.blogspot.com/2009/04/show-grid-view-header-and-footer-when.html

Francesco Carbone from Italy said...

Thanks, Thanks too much!!

Sharon Ojeda said...
This comment has been removed by the author.
M&M said...

I have a problem , when gridview have event rowdatabound this execute first and the controls no works , ShowWhenEmpty execute after rowdatabound event