GridView and CommandArguments
I don't know about you, but I cringe every time I need to create a GridView based layout that needs to create a few custom link handlers off that grid. The typical scenario might be a list where you can toggle certain options or where you can fire an operation that otherwise updates the data that's underlying the grid.
I tend to use business objects in my applications so using the standard data controls doesn't work very well, nor would it really buy much in terms of abstraction. In my code I tend to write the databinding logic as part of the page logic which usually is just a couple of lines of code.
Anyway, the problem isn't that it can't be done 'manually' - but rather that there are a myriad of ways that you can hook up custom link/command processing and they are all similar but yet quite different. None of them feels natural to me, so quite frequently - especially if I haven't written that code in a while - I spent way more time than I should rediscovering what approach I should use the hard way. So I'm taking a few minutes - for my own sake - to write this stuff down so I can look it up next time I have a brain fart <s>.
So there are two different ways that I use frequently for hooking up command links in gridViews.
- ButtonFields and CommandFields
- Template Fields with CommandNames/CommandArguments
ButtonFields and CommandFields
ButtonFields supposedly are the simpler case and you can use a ButtonField with a CommandName.
<asp:GridView ID="dgProviders" runat="server"
onrowcommand="dgProviders_RowCommand"> <columns>
... <asp:ButtonField ButtonType="Link" CommandName="Approval" Text="Approve" /> </columns> </asp:GridView>
Using a ButtonField you can specify a CommandName which in turn provides the OnRowCommand event some context as to what type of operation you are dealing with in CodeBehind. In CodeBehind you have to do a bit of work though if you want to retrieve the proper context from a DataItem. Here's some code that needs to retrieve a Pk of one of the bound items and then perform some work on the data (in this case the business object):
protected void dgProviders_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "Approval") { // *** Retreive the DataGridRow int row = -1; int.TryParse(e.CommandArgument as string, out row); if (row == -1) { this.ErrorDisplay.ShowError("Invalid selection..."); return; } GridViewRow gdrow = dgProviders.Rows[row]; // *** Get the underlying data item - in this case a DataRow DataRow dr = ((DataTable) this.dgProviders.DataSource ).Rows[gdrow.DataItemIndex]; // *** Retrieve our context int pk = (int) dr["pk"]; if (e.CommandName == "Approve") { this.Provider.Entity.Approved = !this.Provider.Entity.Approved; this.Provider.Save(); // *** Update the data source and rebind so we display the right thing dr["Approved"] = this.Provider.Entity.Approved; this.dgProviders.DataBind(); } } }
This seems like a lot of code to have to write just to retrieve data context. The code first has to retrieve the GridViewRow, which is done by retrieving the CommandArgument which in this case a row index of the rendered grid view items. Although the GridViewCommandEventArgs have a DataItem member that member is unfortunately not set with the DataRow as you would maybe expect. So rather than getting the bound item directly, I have to retrieve the gridRow's DataItem index, then use that index and retrieve the value from the original data source.
At that point I have the data I need to be on my way and do my business logic (which in this case is very simple). One advantage using this approach is that I do have access to the underlying data source item (a DataRow here) and so I can update the value and rebind easily and immediately show the updated value(s).
Doesn't it seem very redundant to have to go through all of this just to get at the underlying data item? It seems to me that GridViewEventArgs could have included a consistent mechanism to expose both the DataItemIndex as well as the DataItem itself. Oddly I have no idea why the data item would not be available given that this particular grid is bound to a DataTable which has persistent backing and is live on the form.
The problem with the button field is that it's very limited in functionality. If you need custom text for the caption you can't do that because databinding expressions are not allowed. The following does not work:
<asp:ButtonField ButtonType="Link" CommandName="Approval"
Text='<%# ((bool) Eval(Approved) ? "Approved" : "Unapproved" ) %>' />
because it's not a template container. There is a DataTextField that can be used to display a dynamic value (ie. Approved which would yield True or False only though) and a DataFormatString that can in some cases make this work but for a more dynamic scenario as above that still doesn't do the trick.
Template Columns and Commands
If something more dynamic is required you need to use template fields. Personally I prefer using Template fields most of the time anyway because usually it gives you much more control. Using Template fields with CommandArgument and CommandName also fires the fires the same RowCommand event on the DataGrid. But the handling of the GridViewCommandArgument is quite different.
First here's the implementation of a couple of LinkButtons in the template
<asp:GridView ID="dgProviders" runat="server" CellPadding="4" width="900px" onrowcommand="dgProviders_RowCommand"> <columns>
... <asp:TemplateField ItemStyle-VerticalAlign="Top" ItemStyle-Width="150px" ItemStyle-HorizontalAlign="Center" HeaderText="Action"> <ItemTemplate> <asp:LinkButton runat="server" ID="btnIcon" CommandName="Icon" CommandArgument='<%# Eval("Pk") %>' Text="Remove Icon"></asp:LinkButton><br /> <asp:LinkButton runat="server" ID="LinkButton1" CommandName="Approve" CommandArgument='<%# Eval("Pk") %>' Text="Toggle Approval"></asp:LinkButton> </ItemTemplate> </asp:TemplateField> </columns> <headerstyle cssclass="gridheader" /> <alternatingrowstyle cssclass="gridalternate" /> </asp:GridView>
Here I can specify a command argument which is nice because it lets me be very concise about what data I want to pass to the RowCommand event. In the event handler the code to retrieve the PK in this case becomes considerably simpler, but as we'll see it's a little harder to get a reference to the underlying data source control if that needs to be updated. For clarity I've explicitly broken out each of the objects here:
protected void dgProviders_RowCommand(object sender, GridViewCommandEventArgs e) { int pk = 0; int.TryParse(e.CommandArgument as string, out pk); if (pk == 0) { this.ErrorDisplay.ShowError("Couldn't access provider entry."); return; } if (!this.Provider.Load(pk)) { this.ErrorDisplay.ShowError("Couldn't load provider entry."); return; }
// *** Grab the underlying DataItem WebControl wc = e.CommandSource as WebControl; GridViewRow row = wc.NamingContainer as GridViewRow; int DataItemIndex = row.DataItemIndex; DataRow dr = ( (DataTable) this.dgProviders.DataSource).Rows[DataItemIndex]; if (e.CommandName == "Icon") { Provider.Entity.IconUrl = ""; Provider.Save(); dr["IconUrl"] = ""; } else if (e.CommandName == "Approve") { this.Provider.Entity.Approved = !this.Provider.Entity.Approved; this.Provider.Save(); dr["Approved"] = this.Provider.Entity.Approved; } // *** Rebind with existing data this.dgProviders.DataBind(); }
As you can see getting the PK is now easy - it's simply available as the CommandArgument and so it's easy to get context to the underlying data. However, if you need to get at the actual live data item the work requires first to get the control that fired the event (LinkButton), walking up to its naming container (GridViewRow) and then getting the DataItemIndex. From there I can then retrieve the DataRow based on the index.
Again it seems odd given that we are firing a ROW level RowCommand event that there's not an easier way to get row level context even when we are using a Command argument. <shrug>
Also notice that if you have both ButtonFields/CommandFields and template items that have command arguments, the CommandArgument will contain different things. In the case of a CommandField the CommandArgument is the RowIndex. In the case of the CommandArgument passed on a standard control it's the actual command argument and so you have to very carefully make sure you separate each of those operations. Presumably you should should use one or the other but not both together to avoid confusion.
When it's all said and done neither of these approaches is particularly complex. However, if you've never gone down the path of using a RowCommand event the discoverability of the above code is practically nil. It certainly would be much nicer if the event arguments passed back on these row commands provided a little more information and more consistently. But alas - the above works for now.
Other Posts you might also like
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Getting the Client IP Address in ASP.NET Core
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Resolving Paths To Server Relative Paths in .NET Code
- Adding Default Assemblies, Namespaces and Control Prefixes in Web.Config
The Voices of Reason
# re: GridView and CommandArguments
Maybe I'm missing something, but I've got code that looks like this:
protected void HandleCommand(object sender, GridViewCommandEventArgs e)
{
// Get the index of the row being changed
int rowndx = Convert.ToInt32(e.CommandArgument);
// Get the data key for that row
int pk = (int) grid.DataKeys[rowndx].Value;
// Do more stuff w/ the PK
// ...
// Change stuff on the row
GridViewRow row = grid.Rows[rowndx];
// use row.FindControl("controlID") ...
}
I agree that it would be much easier if the event args included the PK for the row directly, but that seems simpler than what you posted.
Is there a subtlety which makes this not always work?
Donnie
# re: GridView and CommandArguments
# re: GridView and CommandArguments
Josh, yeah the whole paging thing seems lame. I don't see any reason that you HAVE to implement Page_Changed/Changing to set the page number even when ViewState is off. This should just be automatic given that the _doPostback can hold that state information...
# re: GridView and CommandArguments
# re: GridView and CommandArguments
If you have Viewstate turned off on the Gridview, you won't get server-side events unless you rebind your grid manually during Init, which means that you have to have the original list contents in the same sort order (ie, not refreshed from the database, which may have changed). So this implies some kind of persistent store for the list datasource (I tend to use Session).
None of this is very nice or simple, which is not the impression you'd get from reading the MS documentation about databound list controls.
# re: GridView and CommandArguments
For each (GridViewRow orows in Gridview1.Rows)
and Gridview1.FooterRow- this returns only the first footer cells text details. How Do I access the extra footer details????
Please Help!!
# re: GridView and CommandArguments
# re: GridView and CommandArguments
# re: GridView and CommandArguments
will u plz clear my doubt...i already wasted lot of hours to try to get solution for my below doubt...
how to access the cell values of a gridview in a row command event which fires when i click the button control in teplate field of the control..
i already use command argument to get the index..but it shows input string are not in correct format...so will anybody plz clear my doubt...
thanks in advance...
raheema
# re: GridView and CommandArguments
Cheers!
# re: GridView and CommandArguments
I'm using similar code to Rick for a template column / LinkButton with CommandName and Arg.
I googled quite a few articles that were experience an empty string being returned for the command arg with markup similar to CommandArgument='<%# Eval("PK") %>'.
It turns out this problem was because I was debugging and simply refreshing (as I was only changing the ASP). Turns out that wasn't good enough. A recompile was required. I found this strange, and was baffled more by the fact that when I changed the markup to CommandArgument="9", then 9 was passed to the row-command event without a recompile!
# re: GridView and CommandArguments
# re: GridView and CommandArguments
Matt
# GridView and CommandArguments
what is the argument for this command?
# re: GridView and CommandArguments
CommandArgument='<%# Container.DataItemIndex %>'
For example:-
<ItemTemplate> <asp:ImageButton runat="server" ID="btnExpandRow" CausesValidation="false" CommandName="Select" CommandArgument='<%# Container.DataItemIndex %>' ImageUrl="~/images/bullet_arrow_right.gif" Text="Click here to view details" PostBackUrl='<%# GetAppRelativePageUrl() %>' /> </ItemTemplate>
# re: GridView and CommandArguments
# re: GridView and CommandArguments
Like this
CommandArgument='<%#Eval("carid") + ","+Eval("year") %>'
protected void rptrCart_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
ImageButton objibtnEdit = (ImageButton)e.Item.FindControl("ibtnEdit");
string ShipmentIndex = ((CartItem)e.Item.DataItem).SrNo;
string Mode = ((((CartItem)e.Item.DataItem).Mode) == "Export" ? Convert.ToInt32(ShipmentMode.Export).ToString() : Convert.ToInt32(ShipmentMode.Import).ToString());
objibtnEdit.CommandArgument = Mode + "|" + ShipmentIndex;
}
}
</PRE>
And access as
protected void rptrCart_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if (e.CommandName == "Edit")
{
string Mode = e.CommandArgument.ToString().Split('|')[0];
string ShipmentIndex = e.CommandArgument.ToString().Split('|')[1] ;
Session[SessionTypes.SelectedBookingCartItemId] = ShipmentIndex;
Response.Redirect("ShipmentSummary.aspx?Mode=" + Mode);
}
}</PRE>
<P><BR><BR> </P>
# re: GridView and CommandArguments
I have created Template Field in Grid View , contains a Label for Registration id,ImageButton, and three link Button labeled 1, 2, 3 respectively.
The datbase table columns are Regid, MainPhoto,optionalphoto1,optionalphoto2.
Label populated with Eval(regi...)
ImageButton's Imageurl is loaded with "MainPhoto" url from database(using eval function).
after loading, when i click linkbutton 2(ie Text="2"), the image Button should change the imageurl to optionalphoto1, this will happen for link button 3.
i tried a lot but i failed
Email :vinodtvc@yahoo.co.in
<asp:ImageButton ID="ImageButton6" runat="server" Height="145px"
ImageAlign="Middle" ImageUrl='<%#Bind("MainImagePath")%>' Width="120px" />
</div>
<div class="serleft" style="vertical-align:top">
Photo
<asp:LinkButton OnClick ="LinkButton2_Click" ID="LinkButton2" runat="server" Text="1" PostBackUrl='<%#"~/UserPages/frmQuickSearch.aspx?RegID="+Eval("RegID")%>'></asp:LinkButton>
<asp:LinkButton OnClick ="LinkButton3_Click" ID="LinkButton3" runat="server" Text="2" PostBackUrl='<%#"~/UserPages/frmQuickSearch.aspx?RegID="+Eval("RegID")%>'></asp:LinkButton>
<asp:LinkButton OnClick ="LinkButton4_Click" ID="LinkButton4" runat="server" Text="3" PostBackUrl='<%#"~/UserPages/frmQuickSearch.aspx?RegID="+Eval("RegID")%>'></asp:LinkButton></div>
# re: GridView and CommandArguments
I combine your original post with the suggestion of Donnie cause I use paged gridViews and my only need was to know the DataKey Value to extract all data from my SqlDataSource...this is my code:
protected void GridViewMonitor_RowCommand(object sender, GridViewCommandEventArgs e) { GridView gv = (GridView)sender; WebControl wc = e.CommandSource as WebControl; if (wc.ID == "LinkButtonFinalizaEvnt") { LoginView lv = wc.NamingContainer as LoginView; GridViewRow row = lv.NamingContainer as GridViewRow; int reg = (int)gv.DataKeys[row.RowIndex].Value;
# re: GridView and CommandArguments
Last month I was lamenting (to myself) that why are grids still so much work, when so much effort goes into improving other areas of a developer's life? A lot of improvement is needed here!