Friday, June 22, 2007

GridView with Multiple Rows Selected, Controls in the Template Column and Edit Mode

It is common to implement selection of multiple rows in a  GridView with a help of checkbox control embedded in the TemplateColumn.  You will have to write something like this in your ASPX.

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
DataSourceID="SqlDataSource" >
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="CheckBox1" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Name" HeaderText="Name" />
<asp:BoundField DataField="ModifiedDate" HeaderText="ModifiedDate" />
<asp:CommandField ShowEditButton="True" />
</Columns>
</asp:GridView>



However, if you also need to to support data editing in the GridView and you add CommandField column with ShowEditButton="True" you will find that your solution does not work as desired. 




If you click on "edit" link when you have several rows selected in your GridView, you lose your selection.  This behavior does not depend on EnableViewState value.




This strange behavior is explained by the following:




CheckBox control restores its Checked property from the post-data submitted by the browser on post-back.  Post-data processing triggers data binding of the GridView control and post-data are processed before the OnLoad phase in the page lifecycle.  However, when GridView handles Click event on [Edit] LinkButton in the command column, GridView needs to redo data binding again to create edit row.  This second data binding in the page lifecycle create new CheckBox controls which never process post-data and their Checked property value remains false.  It can be easily seen if you enable page Trace and handle DataBinding event in the GridView by the following handler:

    protected void GridView1_DataBinding(object sender, EventArgs e) {
Trace.Write("DATABINDING");
}



Now, when you click on [Edit] link in any row, your trace will be similar to: 




ASPTrace




Note two DATABINDING twice in the trace log.




Solution




The simplest solution that works for me is to store references to selected rows in the ViewState and restore state of checkboxes on data binding.  This can easily accomplished by:

    protected void CheckBox1_CheckedChanged(object sender, EventArgs e) {
CheckBox checkbox = (CheckBox)sender;
if (checkbox.Checked) {
ViewState[checkbox.UniqueID] = true;
}
else {
ViewState.Remove(checkbox.UniqueID);
}
}

protected void CheckBox1_DataBinding(object sender, EventArgs e) {
CheckBox checkbox = (CheckBox)sender;
checkbox.Checked = ViewState[checkbox.UniqueID] != null;
}



CheckBox must be configured as:

    <asp:CheckBox ID="CheckBox1" runat="server" 
OnCheckedChanged="CheckBox1_CheckedChanged" OnDataBinding="CheckBox1_DataBinding"/>



It requires page ViewState to be enabled, of course.




UPDATE:




Sure this solution will not work with inserts or deletes being made to data source, unless you replace checkbox.UniqueID with the value of key field in the data source.

9 comments:

Paul Schroeder said...

I was unable to use this solution because of filtering that changes the data that goes into eack UniqueID property.

Instead, since I am using AJAX I made use of the
ScriptManager.GetCurrent(this).IsInAsyncPostBack

property inside of the CheckedChanged event. This eliminates processing during the second databinding call when we don't have the true checked state of the control. See below:

protected void chkRemove_CheckedChanged(object sender, EventArgs e)
{
if (ScriptManager.GetCurrent(this).IsInAsyncPostBack)
{
CheckBox checkbox = sender as CheckBox;
long eiaid = long.Parse(checkbox.Attributes["xID"]);

// Do Something...
}
}

protected void gvDetail_RowDataBound(object sender, GridViewRowEventArgs e)
{
CheckBox chkRemove = FindControl("chkRemove");
long eiaid = (long)DataBinder.Eval(e.Row.DataItem, "Element_Instance_Attribute_ID");

chkRemove.Attributes.Add("xID", eiaid.ToString());
chkRemove.Checked = (DataBinder.Eval(e.Row.DataItem, "IsRemove").ToString() == "0") ? false : true;
}

Yuriy Solodkyy said...

Hi Paul,

If you can store checked value in your data source that is the easiest solution. However, it is not always possible to update you original data source. In this case, ViewState approach can also work. UniqueID cannot be used if you apply filter, but you can retrieve row key value and use it instead of UniqueID as ViewState key. In your sample you could use Element_Instance_Attribute_ID as key for the ViewState.

badari said...

how can i get source file of this program

Yuriy Solodkyy said...

I will restore this attachment today in few hours. Please check again several hours later.

Bjarne Havnen said...

Excactly what I needed to get going. I added an attribute to the checkbox where I saved the database row id, used this attribute as value for ViewState and it works perfect. Combined that with a hidden checkboxlist so I could easily process the selections.

adriakuma said...

Awesome recomendation from Paul Schroeder

protected void gvDetail_RowDataBound(object sender, GridViewRowEventArgs e)
{
CheckBox chkRemove = FindControl("chkRemove");
long eiaid = (long)DataBinder.Eval(e.Row.DataItem, "Element_Instance_Attribute_ID");

chkRemove.Attributes.Add("xID", eiaid.ToString());
chkRemove.Checked = (DataBinder.Eval(e.Row.DataItem, "IsRemove").ToString() == "0") ? false : true;
}

very thanks from Mexico.

Edward M. Meshuris said...

Hi,
Thank you for typing your solution, and for small grids, it may be an actual solution.
But try this with a few thousand rows, and you will see that the viewstate that will be generated is over 5mb.
Couple that with a postback on every click, and you get an unusable page.
I would suggest that you look at replacing the asp checkbox with a regular html checkbox, and doing all of the event handling with javascript. The preferred solution would assign the event handlers at page load, that way you only traverse the dom once.

Yuriy Solodkyy said...

Hi Edward, without regard to the sampe code I believe that you shoukd never have grids with thousand rows even with paging. Have you ever tried to work with such grids? I prefer to think that there is always better way to organize UI.
The sample shows some techniques. Nothing more.

Anonymous said...

Hi
can you please provide source code for the sample gridview?
Thanks in advance.