Introduction
There are several online sources describing how to make a GridView
row clickable anywhere on the row. There are also a few which describe how to make a row double-clickable. I haven't come across any which handle both events or deal with event validation errors in ASP.NET 2.0.
Here I will describe how to handle single and double clicks with the GridView
, DataList
and ListBox
controls. I will also show how to deal with event validation without disabling it.
Background
While developing an ASP.NET application to replace an old Windows application, I was asked to keep the user experience relatively close to the original. The original application let a user select a row and perform an action on that row from a toolbar. By double clicking on a row the user could open another form with a details view of that row. This type of functionality could easily be achieved with two separate buttons on each row of a GridView
but I needed something better than that.
Single and Double Clicking with a GridView
Create an new web application with a default page. For now, add EnableEventValidation="false"
to the page directive. We will remove it later. Add a GridView
and bind some data to it. Add two asp:ButtonField
controls using the edit columns option on the GridView
. Give these button fields command names of SingleClick
and DoubleClick
. (A select button could also be used for the SingleClick
event but I've decided to use asp:ButtonField
for both.)
<Columns>
<asp:ButtonField Text="SingleClick" CommandName="SingleClick" />
<asp:ButtonField Text="DoubleClick" CommandName="DoubleClick" />
</Columns>
Create a RowCommand
event for the GridView
with a switch
block to capture the separate events.
For the demo, this code will write out a history of the events fired. The SingleClick
command will also set the SelectedIndex
of the row.
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
GridView _gridView = (GridView)sender;
int _selectedIndex = int.Parse(e.CommandArgument.ToString());
string _commandName = e.CommandName;
switch (_commandName)
{
case ("SingleClick"):
_gridView.SelectedIndex = _selectedIndex;
this.Message.Text += "Single clicked GridView row at index "
+ _selectedIndex.ToString() + "<br />";
break;
case ("DoubleClick"):
this.Message.Text += "Double clicked GridView row at index "
+ _selectedIndex.ToString() + "<br />";
break;
}
}
Create a RowDataBound
event to modify each row as it is bound. Now we need to take the client script which is used by the SingleClick
button for postback and assign it to the entire row.
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton _singleClickButton = (LinkButton)e.Row.Cells[0].Controls[0];
string _jsSingle =
ClientScript.GetPostBackClientHyperlink(_singleClickButton, "");
e.Row.Attributes["onclick"] = _jsSingle;
}
We can also do the same for the DoubleClick
button, however both of these will not work together.
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton _doubleClickButton = (LinkButton)e.Row.Cells[1].Controls[0];
string _jsDouble =
ClientScript.GetPostBackClientHyperlink(_doubleClickButton, "");
e.Row.Attributes["ondblclick"] = _jsDouble;
}
If we implement both these events together we will only get the functionality of the single click. This is because when the user starts a double click, the first click is taken as a single click and the page is posted back before the second click can happen. The JavaScript setTimeout
method can be used to set a timeout on the first click, therefore giving the user a chance to complete a double click.
Looking at the source of the page, we can see the onclick
event on the <tr>
tags:
onclick="javascript:__doPostBack('GridView1$ctl02$ctl00','')"
Add a setTimeout
method and give it a timeout of 300 milliseconds:
onclick="javascript:setTimeout
("__doPostBack('GridView1$ctl02$ctl00','')", 300)"
So the entire RowDataBound
code looks like:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton _singleClickButton = (LinkButton)e.Row.Cells[0].Controls[0];
string _jsSingle =
ClientScript.GetPostBackClientHyperlink(_singleClickButton, "");
_jsSingle = _jsSingle.Insert(11, "setTimeout(\"");
_jsSingle += "\", 300)";
e.Row.Attributes["onclick"] = _jsSingle;
LinkButton _doubleClickButton = (LinkButton)e.Row.Cells[1].Controls[0];
string _jsDouble =
ClientScript.GetPostBackClientHyperlink(_doubleClickButton, "");
e.Row.Attributes["ondblclick"] = _jsDouble;
}
}
The buttons no longer need to be visible so hide them by adding Visible="false"
:
<Columns>
<asp:ButtonField Text="SingleClick" CommandName="SingleClick"
Visible="false" />
<asp:ButtonField Text="DoubleClick" CommandName="DoubleClick"
Visible="false" />
</Columns>
We can now single click and double click on a row and the appropriate action will be captured in the RowCommand
code.
Register the postback or callback data for validation
Everything is working fine, but remember that we added EnableEventValidation="false"
to the page directive. This is not the most secure option so we should remove it. This will cause an "Invalid postback or callback argument" error when a row is clicked or double clicked. The error tells us to use the ClientScriptManager.RegisterForEventValidation
method in order to register the postback or callback data for validation. (The purpose of EventValidation
is well documented elsewhere and beyond the scope of this article.)
The ClientScriptManager.RegisterForEventValidation
can be called by overriding the Render
method. The trick here is to register the unique id of both the buttons for each row of the GridView
. The UniqueID
of the row is returned by GridViewRow.UniqueID
. The UniqueID
of the first button can be generated by appending "$ctl00
" to the row's UniqueID
and for the second button append "$ctl01
".
The overridden Render
method is as follows:
protected override void Render(HtmlTextWriter writer)
{
foreach (GridViewRow r in GridView1.Rows)
{
if (r.RowType == DataControlRowType.DataRow)
{
Page.ClientScript.RegisterForEventValidation
(r.UniqueID + "$ctl00");
Page.ClientScript.RegisterForEventValidation
(r.UniqueID + "$ctl01");
}
}
base.Render(writer);
}
Now we will not get any "Invalid postback or callback argument" errors.
Other Controls
In the source code, I also demonstrate how to achieve this functionality with a DataList
and ListBox
controls. The code is quite similar so I won't show it here.
Single and Double Clicking with a DataList
With the DataList
, the contents of the ItemTemplate
are wrapped in an asp:Panel
, giving something to assign the onclick
and ondblclick
events to.
Single and Double Clicking with a ListBox
With the ListBox
, there are no Row or Item events but the onclick
and ondblclick
attributes can be added to the control.
In the SelectedIndexChanged
event handler, Request.Form["__EventArgument"]
will return the command name.
Conclusion
By looking closely at the JavaScript which is written out by ASP.NET we can extend the functionality of the GridView
and DataList
controls. To keep your applications secure, you should not use EnableEventValidation="false"
if at all possible. Once you understand how EventValidation
works, you can use ClientScript.RegisterForEventValidation
to ensure your code works as intended.
History
- v1.0 - 23rd Sep 2006
- v1.1 - 25th Sep 2006
- v1.2 - 12th Nov 2006
Addition of a ListBox
control to the demo - v1.3 - 20th Dec 2006
VB.NET version of demo added to download