Riddle-me this RowCommand

Onion Blog

Syndication

I've been working on a project recently that involves using the new GridView in ASP.NET 2.0 a fair amount. In general, I'm quite pleased with this new grid implementation - it feels more intuitive, more extensible, and lighter weight than the monstrosity that is the DataGrid.
There's one thing that's bugging me, however, since I can't find a simple way to do it and it is such a common thing in Web programming that I feel I must be overlooking something: to add a command column to the grid, the handler of which uses the current row of data to perform some task. The most common thing is to have a hidden primary key field which you then access through the Cells array of the current row (at least this was the technique in 1.1 with the DataGrid).
There is a nice ButtonField that you can add to your columns that looks like it should do the trick:
 
<asp:ButtonField CommandName="use" HeaderText="Use" Text="Use" />
 
then you can add a handler for the RowCommand event of the GridView and look for the "use" command name (this seems analogous to the ButtonColumn/ItemCommand pair in the DataGrid).
 
  protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
  {
    if (e.CommandName == "use")
    {
    }
  }
 
So far so good. The first problem is that the GridViewCommandEventArgs has no reference to the current row (you could access it in the old DataGrid's DataGridCommandEventArgs.Item property). Ok, no problem - with a little spelunking you can use the CommandSource property and walk up to its grandparent and that turns out to the be the row - not the prettiest code, but at least we have the row.
 
GridViewRow row = (GridViewRow)((Control)e.CommandSource).Parent.Parent;
 
Now, I try and grab the value of the hidden ID column at index 0 (again, a common technique with the DataGrid).
 
string id = row.Cells[0].Text;
 
But the cell has no text in it! After some spelunking in the debugger, the value of the hidden primary key column does not seem to be there at all. As you can see, it feels like I'm swimming upstream here, and that I must be missing some new feature in the GridView that provides this same functionality. If anyone knows what that is, please post a comment.
 
If you have the same problem and are looking for a solution, here's what I ended up doing - just using a template field with a LinkButton and passing the ID in the CommandArgument field:
 
<asp:TemplateField>
  <ItemTemplate>
    <asp:LinkButton ID="useLinkButton"
                         CommandArgument='<%# Eval("id") %>' Text="Use"
                         runat="server" OnCommand="useLinkButton_Command" />
  </ItemTemplate>
</asp:TemplateField>
 
protected void useLinkButton_Command(object sender, CommandEventArgs e)
{
  string id = (string)e.CommandArgument;
  // do task with id here
}
 
 

Posted Jun 24 2005, 07:59 AM by fritz-onion
Filed under:

Comments

peter@peterkellner.net wrote re: Riddle-me this RowCommand
on 06-24-2005 6:57 AM
Fritz,

I think you are fighting with the same issue I did a while back. In beta 2, for security, the ASP team removed hidden fields the viewstate. The suggeste way to get to data is using the datakeynums property. Check out this discussion for the gory details. I now use datakeynums exclusively and am very happy. Also, be sure to read the one about when the viewstate gets encrypted.

Here is the URL

http://forums.asp.net/897693/ShowPost.aspx

Fritz Onion wrote re: Riddle-me this RowCommand
on 06-24-2005 7:18 AM
Thanks Peter - that's exactly it. So this now works with the RowCommand:

GridViewRow row = (GridViewRow)((Control)e.CommandSource).Parent.Parent;

string id = (string) employeeGridView.DataKeys[row.DataItemIndex].Value;

But it still feels like it should be simpler - do you know of an easier way to get the row index from the GridViewCommandEventArgs?
peter@peterkellner.net wrote re: Riddle-me this RowCommand
on 06-24-2005 3:34 PM
This is the two lines of code I always use:

GridView gv = (GridView)sender;
id_file = (int)gv.DataKeys[gv.SelectedIndex].Values[0];

I don't bother with the EventArgs, but I'm sure somehow what I am doing is bad. But on the otherhand, parent.parent kind of scares me.

So, does this redeem me from answering your question at devcon with the answer httpservlet? I am going to javaone next week so I'm still not pure.
jdevesa@ono.com wrote re: Riddle-me this RowCommand
on 06-26-2005 1:38 AM
Hello. Nice tip this one. A couple of days ago we were fighting with this issue. Finally, after reading in ASP.NET Forums, we found out how to accomplish this task.

However, It is frustating that something that was so easy to achieve in the DataGrid, turned into something slightly complicated.

If you use for example .NET Reflector, you'll be very surprised because the GridViewCommandEventArgs class has a read-only property called Row which returns a GridViewRow object. Neither Intellisense in VS 2005 nor the Beta Doc gives any info at all about her. So the functionality to get the "Row" it is implemented but it is not accessible.

Security is the reason provided by MS... but if this is the real fact, what have changed in ASP.NET 2 that this simple issue now it isn't available but it was on previous version?

Thanks and apologize my english.
Christopher Steen wrote Link Listing - June 26, 2005
on 06-26-2005 8:41 PM
Link Listing - June 26, 2005
Brett wrote re: Riddle-me this RowCommand
on 06-26-2005 11:43 PM
I may have missed something from your post but I would have thought the following (which is the way i've been doing it) was the simplest:

<asp:GridView id="GridView1" runat="server" DataKeyNames="Key1,Key2">

GridView gv = (GridView)sender;
id_file = (int)gv.SelectedDataKey.Values["Key1"];

http://msdn2.microsoft.com/library/3t1ce01y(en-us,vs.80).aspx

http://quickstart.developerfusion.co.uk/QuickStart/aspnet/doc/data/advanced.aspx#masterdetails
Fritz Onion wrote re: Riddle-me this RowCommand
on 06-27-2005 6:07 AM
Brett (and Peter's last example) both rely on the 'Select' feature being used (at least in my experiments) which I was not using. I'm just using the generic CommandField.

jdevesa - I saw that internal property on the GridViewCommandEventArgs and felt the same frustration. I'm not sure why it's not available publicly as it is in the GridViewRowEventArgs.
vby wrote re: Riddle-me this RowCommand
on 06-30-2005 11:09 AM
hey Fritz
If you still need to have an index in CommandArgument, you can bind it manually like this:

CommandArgument='<%# DataBinder.Eval(Container.DataItemIndex) %>'

and then just refer to appropriate row in Rows collection of your grid
yaip wrote re: Riddle-me this RowCommand
on 07-03-2005 12:16 PM
vby - thx. i have been having the same frustration. you solution is simple yet elegant.
Mark Wilcock wrote re: Riddle-me this RowCommand
on 07-11-2005 3:44 AM
If you can use the bind the commandArgument to the value in another column, this makes things simpler

<asp:BoundField DataField="TradeId" HeaderText="TradeId" />
...
<asp:TemplateField HeaderText="Approve?" >
<ItemTemplate>
<asp:LinkButton runat=server ID=ApproveLinkButton CommandName=Approve Text=Approve CommandArgument='<%# Bind("TradeId") %>' />
</ItemTemplate>
</asp:TemplateField>


protected void GridView_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Approve")
{
DoSomething(e.CommandArgument)
...
}
}
Teemu Keiski wrote re: Riddle-me this RowCommand
on 07-28-2005 11:34 PM
I use it also pretty much like with DG. Assuming I have DataKeyNames set, then I just do in RowCommand

...
Dim row As GridViewRow = CType(CType(e.CommandSource, Control).NamingContainer, GridViewRow)

Dim id As Integer = CInt(GridView1.DataKeys(row.RowIndex).Value)
...
Almnost like you do (except that I replace Parent.Parent with NamingContainer)

If you have a ButtonField or a Button with CommandName="Select" then you get the row from GridView's SelectedRow property as well as the index either from the row's RowIndex or GridView's SelectedIndex property. DataKey can also then get via GridView's SelectedDataKey property.
Zoltan Grose wrote re: Riddle-me this RowCommand
on 08-02-2005 12:40 PM
The "normal" way to get the index of the row when using the built-in commands is from the CommandArgument.

And thanks to this blog I can now read the Id value too. =)

So the boilerplate GridView code is:

protected void GridView1_RowCommand(Object sender, GridViewCommandEventArgs e) {

if (e.CommandName == "Edit") {
int index = Convert.ToInt32(e.CommandArgument);
String id = (String)this.GridView1.DataKeys[index].Value;
// do something with the id
}
}
Ali wrote re: Riddle-me this RowCommand
on 08-04-2005 6:45 PM
If you're using a Button Command it's pretty straight forward. How do you get the index if you're using a LinkButton in a Template? I've been struggling with that.
Larry wrote re: Riddle-me this RowCommand
on 08-06-2005 12:12 AM
Hey... GREAT thread!
I'm having a strange issue. I have a button field "Details" with a row command handler like this:

Protected Sub GridView_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs)
Dim appid As String

If e.CommandName = "Details" Then
db_id = GridView.DataKeys(e.CommandArgument).Value.ToString()
'do stuff with db_id
'it's the data base index I'm interested in.
End If
End Sub

My customer notices that when the database is changing regularly and they click on the Details button, it occasionally returns the wrong row. This makes me think that the wrong row index is getting returned as the table is growing. Does this thing auto bind? If so, that could explain this.

Any ideas?
joe wrote re: Riddle-me this RowCommand
on 12-12-2005 11:29 AM
// Convert the row index stored in CommandArgument
// property to an Integer.
int index = Convert.ToInt32(e.CommandArgument);
Tim Mackey's Weblog wrote GridView - accessing row information inside RowCommand
on 12-20-2005 8:56 AM
gozh2002 wrote re: Riddle-me this RowCommand
on 02-13-2006 4:51 AM
what if I set the style sheet like display:none,
will it work?
emailpassion wrote re: Riddle-me this RowCommand
on 02-13-2006 1:28 PM
Same problem and same question :
I try to get in the GridView_RowCommand, the index of the row in which I have put a button with CommandName "AddToCart".

Using
int idx = Convert.ToInt32(e.CommandArgument);
it works only if my button is defined as a ButtonField in EditColumn.
But the same code doesn't work if I use it for a Button put in an ItemTemplate.

I got the following error

System.FormatException was unhandled by user code
Message="Input string was not in a correct format."
Source="mscorlib"
...

My RowCommand method has the same code in both cases :

protected void GVwProducts_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName != "AddCart")
return;
if (e.CommandName == "AddCart")
{
int idx = Convert.ToInt32(e.CommandArgument);
CheckBox selchkbx = ((CheckBox)(GVwProducts.Rows[idx].FindControl("ChkSelect")));
.... }

Any idea of the solution for the ItemTemplate Button ?

Justin Wignall wrote re: Riddle-me this RowCommand
on 02-24-2006 4:20 AM
emailpassion:

AFAIK You need to explicitly set the CommandArgument on the ItemTemplate button

using either

CommandArgument='<%# Container.DataItemIndex %>'

as shown above by vby or by eval'ing a particular item - in your case

CommandArgument='<%# Eval("idx")%>'
Lex O'Connor wrote re: Riddle-me this RowCommand
on 03-22-2006 4:33 PM
Exellent

This example helped me find the solution.

Thanks Lex
David wrote re: Riddle-me this RowCommand
on 04-03-2006 11:41 AM
If you have the datagrid autogenerateselectbutton="true", AND you have the datakeynames set to a value (Primary key), you can use...

MyGridView.SelectedValue

This is the datakeyvalue of the currently selected row.

This should work for a button/link etc in the rows of the gridview as well.


Thanks,

David
David wrote re: Riddle-me this RowCommand
on 04-03-2006 11:54 AM
hmm. i think you can scrap my last post. selectedvalue is going to get you the "last known" selected row, not the current one. there is probably a work around (thinking that through as I go), but wanted to discard that last post...
James wrote re: Riddle-me this RowCommand
on 05-03-2006 7:00 AM
Thanx... I've tried to figure this out the whole day... Finalllllly!!! :D
Relket wrote re: Riddle-me this RowCommand
on 05-10-2006 7:02 AM
If you want to code more easy this issue, go here:

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

:)
Kalvin wrote re: Riddle-me this RowCommand
on 05-16-2006 9:09 PM
I have a question that sort of relates to this that someone might be able to answer.

Is it possible to programmatically alter the value of the hidden field at a specified row of the gridview (without doing a whole new databinding operation) ?

Cheers
Kalvin
Rob wrote re: Riddle-me this RowCommand
on 06-12-2006 11:17 AM
if ur still looking, heres a slick one liner for ya:
string selectedItem = ((GridView)e.CommandSource).DataKeys[Convert.ToInt32(e.CommandArgument)].Value.ToString();
asif wrote re: Riddle-me this RowCommand
on 06-12-2006 11:56 PM
why e.CommandArgument returns an empty string?

My code is here

Dim index As Integer = Convert.ToInt32(e.CommandArgument)
Raj wrote re: Riddle-me this RowCommand
on 06-19-2006 9:25 AM
<asp:LinkButton ID="lnk_AK" runat="server" Visible="false" CommandName="KEY" CommandArgument='<%# Container.DataItemIndex %>' >Check</asp:LinkButton>

This is the perfect solution to get Index of Selected Row and U can use below code for manipulation on rowcommand

Protected Sub gv_Chk_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles gv_Chk.RowCommand

If e.CommandName = "KEY" Then


Dim index As Integer = Convert.ToInt32(e.CommandArgument)

Dim gv As GridView
gv = New GridView
gv = Me.Master.FindControl("MainContent").FindControl("gv_Chk") ' I m using Master page so..i used MainContent..If u r not using, use only Me.FindControl("gv_Chk")


Dim row As GridViewRow = gv.Rows(index)

Dim txt As TextBox = New TextBox
txt = row.FindControl("txt_Key")

Dim txt_id As TextBox = New TextBox
txt_id = row.FindControl("txt_SubHead_Id")
Response.Write(txt.Text)

End If
End Sub
Joel Reinford wrote re: Riddle-me this RowCommand
on 07-05-2006 3:40 AM
I'm getting an exception with this when the gridview has paging enabled and I'm selecting a row that is not on the first page. The Exception message is "Index was out of range. Must be non-negative and less than the size of the collection."

The row index is being sent correctly by the command argument. It looks as though the gridview doesn't know anything about that index because it only has the keys for the current page. That is, if the gridview has a page size of 20 and the user clicks on row #10 on page 2, the index is 29 but the gridview only knows about 20 rows.

I've implemented a work-around:
Dim idx As Integer = Convert.ToInt32(e.CommandArgument)
Dim rowIndex As Integer = idx
'pager work-around
If grid.PageIndex = 0 Then
rowIndex = idx
Else
rowIndex = idx - (grid.PageIndex * grid.PageSize)
End If

Any suggestions on a better way to do this would be appreciated. It doesn't seem like this should be needed and nobody else has mentioned any problems with paging.

Joel Reinford
Data Management Solutions LLC
Will wrote re: Riddle-me this RowCommand
on 07-11-2006 12:00 PM
Thank sooo much!
Michael Appelmans wrote re: Riddle-me this RowCommand
on 07-19-2006 10:26 PM
Why is my DataItem null?
protected void gvVanPoolList_RowCommand( object sender, GridViewCommandEventArgs e ) {
if ( e.CommandName=="ShowLog" ) {
// Convert the row index stored in the CommandArgument
// property to an Integer.
int index = Convert.ToInt32((string)e.CommandArgument);
GridViewRow gvr = gvVanPoolList.Rows[index];
DataRowView drv = (DataRowView)gvr.DataItem;


}
}
Shaun Kayne wrote re: Riddle-me this RowCommand
on 08-01-2006 5:31 AM
I have struggled for an entire day to get this damn thing working. I was trying it in a user control, but eventually moved the grid to the content page.
Add DataKeys to the grid.
protected void gvList_RowClick(object sender, GridViewCommandEventArgs e)
{
int index = Convert.ToInt32(e.CommandArgument);
txtBrand.Text = gvList.DataKeys[index].Values[0].ToString();
}

This will give you the data value per row clicked. Values[0] the first data key, Values[1] the second and so on.
Michel Grootjans wrote re: Riddle-me this RowCommand
on 08-04-2006 6:37 AM
Did you guys realise the following:

When you subscribe to the RowCommand event of a DataView, you can extract the actual row that was called. At that time, the row's DataItem is null.

When a DataView is DataBound, you can subscribe to the RowDatabound event. This event has a GridViewRowEventArgs, which holds a property Row. At this time however, the row's DataItem holds a reference to the bound object, whatever type it is.

Why is this?
Michel Grootjans wrote re: Riddle-me this RowCommand
on 08-04-2006 6:39 AM
from the helpfile(http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.gridviewrow.dataitem(d=ide).aspx):

Use the DataItem property to access the properties of the underlying data object to which the GridViewRow object is bound. The DataItem property is only available during and after the RowDataBound event of a GridView control.
Ade wrote re: Riddle-me this RowCommand
on 08-21-2006 4:30 PM
Hi there

I was experiencing the same problem with the rowcommand event when I stumbled upon your blog. Thanks everyone for the good advice.

After doing a bit more research it looks like the e.CommandArgument returns the index of the current row. You can use this to reference to required cell that contains the value you are after e.g.



Dim MyGridView As GridView = CType(Me.FormView1.FindControl("GridView1"), GridView)

Dim intID as integer = ctype(MyGridView.Rows(e.CommandArgument).Cells(2).Text,integer)
Digital_James wrote Gridview Problems
on 08-30-2006 6:18 AM
If you're having issues with getting the current row in the RowCommand function or having problems getting...
dms wrote re: Riddle-me this RowCommand
on 09-19-2006 1:53 PM
WILL NOT WORK IN MULTI-USER ENVIRONMENT!

Okay, now that I got your attention, I just want to make sure this was stated if it hasn't been already: In a multi-user environment, it is very possible that the row index could change between the time the user first views a page and when they click a row. You cannot rely on e.CommandArgument to have the correct index. Then, you cannot rely on any code that was based off of this index.

Example: User1 loads grid with 100 rows. User2 deletes record number 50. User1 clicks on row 60. When the page posts back and the data is reloaded/rebinded, the row the user1 clicked on is now actually in position 59, not 60... but the e.CommandArgument will say 60. Then, when user1 gets the ID/Key or any data from row 60, it's actually the wrong data... User1 will take an action on the wrong row.

I think Larry who posted 8/6/2005 2:12 AM had it correct, and please don't blindly follow the MSDN examples as suggested by Relket 5/10/2006 9:02 AM.

Am I wrong??? (It would be great news if I am!)

Thank you!




http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.rowcommand.aspx
Mehmet Âkif wrote if GridView1.Columns[0].Visible is false ???
on 10-06-2006 3:05 AM
if GridView1.Columns[0].Visible is false then
i did this :)
GridView1.Columns[0].Visible = true;
GridView1.DataBind();
hdn_kod.Value = GridView1.SelectedRow.Cells[0].Text;
GridView1.Columns[0].Visible = false;
Response.Write("is it uninterested")
end if
Ken Palmer wrote re: Riddle-me this RowCommand
on 10-09-2006 1:38 PM
Fritz,
Thanks for posting this blog. I saw you at VS Live, and you were one of the best presenters there.

At any rate, I spent all day working on this problem. DMS made a great point (posted 9/19/2006); you can't depend on the command index in a multi-user environment.

Here is my solution. I have a user control with a sortable, paginated GridView that has 4 image buttons (Print, Edit, Copy, and Delete). The DataKey for this grid is a GUID named DocumentToken. As you'll see, the DataKey is not relevant to the discussed portion of this solution indicated above. In addition to addressing the above, I've also included the delete confirmation code as well. It's likely that anyone looking for this will need something to that effect. Admittedly, this isn't elegant, but it works.

The short explanation is this. Associate a command argument with each image button, passing the DataKey to this element, like so:
CommandArgument='<%# Eval("DocumentToken") %>'

Remember to use single quotes after the equals sign or you'll get a parsing error.

Call a routine from the OnRowCommand event of the GridView. In this example, this appears as: OnRowCommand="grdMenu_OnRowCommand". From that routine, use System.Web.UI.WebControls.GridViewCommandEventArgs.CommandArgument to get the datakey.

############################################
Here is the relevant code from the .aspx:
############################################
<script language="javascript" type="text/javascript">
function Ask(argControl, argDocumentToken){
alert(argDocumentToken);
elParentRow = argControl.parentNode.parentNode // Control=ImgButton, Parent=TD, Parent=TR
cellDate = elParentRow.getElementsByTagName("td")[1].innerHTML
cellCustomer = elParentRow.getElementsByTagName("td")[2].innerHTML
cellDescription = elParentRow.getElementsByTagName("td")[3].innerHTML
Message = "Are you sure you want to delete this item?\n\nDate: " + cellDate + "\nCustomer: " + cellCustomer + "\nDescription: " + cellDescription;
Message = Message.replace(/&amp;/gi,"&");

if(confirm(Message)){
return true;
} else {
return false;
}
}
</script>

<asp:GridView ID="grdMenu" runat="server"
SkinID="GridMenu" AutoGenerateColumns="True"
DataKeyNames="DocumentToken"
OnRowCreated="grdMenu_RowDataBound"
OnRowCommand="grdMenu_OnRowCommand"
>
<Columns>
<asp:BoundField HeaderText="Requester" DataField="RequesterName" SortExpression="RequesterName, RequestedDate DESC"/>
<asp:BoundField HeaderText="Date Requested" DataField="RequestedDate" SortExpression="RequestedDate" DataFormatString="{0:M/d/yy hh:mm tt}" HtmlEncode="False">
<HeaderStyle Width="110px" />
</asp:BoundField>
<asp:BoundField HeaderText="Customer" DataField="LabelName" SortExpression="LabelName, RequestedDate DESC">
<HeaderStyle Width="150px" />
</asp:BoundField>
<asp:BoundField HeaderText="Description" DataField="TaskDescription" SortExpression="TaskDescription, RequestedDate DESC" />
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgBtnPrint"
runat="server" CausesValidation="False"
ImageUrl="~/Images/Icon_Printer.gif" AlternateText="Print"
CommandName="DocumentPrint"
CommandArgument='<%# Eval("DocumentToken") %>'
/>
</ItemTemplate>
<ItemStyle BorderStyle="None" HorizontalAlign="Center" Width="20px" />
<HeaderStyle BorderStyle="None" />
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgBtnEdit"
runat="server"
CausesValidation="False"
ImageUrl="~/Images/Icon_Edit.gif" AlternateText="Edit"
CommandName="DocumentEdit"
CommandArgument='<%# Eval("DocumentToken") %>'
/>
</ItemTemplate>
<ItemStyle BorderStyle="None" HorizontalAlign="Center" Width="20px" />
<HeaderStyle BorderStyle="None" />
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgBtnCopy"
runat="server"
CausesValidation="False"
ImageUrl="~/Images/Icon_Copy.gif"
AlternateText="Copy"
CommandName="DocumentCopy"
CommandArgument='<%# Eval("DocumentToken") %>'
/>
</ItemTemplate>
<ItemStyle BorderStyle="None" HorizontalAlign="Center" Width="20px" />
<HeaderStyle BorderStyle="None" />
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgBtnDelete"
runat="server"
CausesValidation="False"
ImageUrl="~/Images/Icon_Delete.gif"
AlternateText="Delete"
CommandName="DocumentDelete"
CommandArgument='<%# Eval("DocumentToken") %>'
/>
</ItemTemplate>
<ItemStyle BorderStyle="None" HorizontalAlign="Center" Width="20px" />
<HeaderStyle BorderStyle="None" />
</asp:TemplateField>
</Columns>
</asp:GridView>

############################################
Here is the relevant code from the .aspx.vb:
############################################

Protected Sub grdMenu_RowDataBound(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _
Handles grdMenu.RowDataBound

'Delete button.
If e.Row.RowType = DataControlRowType.DataRow Then
Dim btnDelete As ImageButton = e.Row.FindControl("imgBtnDelete")
If btnDelete IsNot Nothing Then
Dim DocumentToken As String = Convert.ToString(DataBinder.Eval(e.Row.DataItem, "DocumentToken"))
btnDelete.OnClientClick = "return Ask(this, '" & DocumentToken & "')"
End If
End If

End Sub

Public Sub grdMenu_OnRowCommand(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) _
Handles grdMenu.RowCommand

Dim imgButton As String = e.CommandName
Select Case imgButton.ToLower
Case "documentcopy", "documentdelete", "documentedit", "documentprint"
Dim DocumentToken As String = e.CommandArgument.ToString()
Dim SelectedButton As String = e.CommandName
Response.Write("<br>SelectedButton: " & SelectedButton)
Response.Write("<br>DocumentToken: " & DocumentToken)
Response.End()
End Select

End Sub

Again, this isn't an elegant solution; but it works. There's probably a better way to do the above. Thanks.