ASP.NET Today: GridView Gripes
The following was a rather common task to accomplish with DataGrids. Users click a button in a button column (say, Add to Cart), a server-side event fire (ItemCommand), you handle it and do what's needed. This basic pattern doesn't change with GridViews, but some details are different and require different approaches.
First and foremost, the index of the clicked row is not carried by the event data structure (as with DataGrids), but can be found through the CommandArgument property. Here's how:
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Add"))
{
int index = Convert.ToInt32(e.CommandArgument);
:
}
}
The Beta 2 documentation mentions you should put the index in the CommandArgument manually by writing a RowCreated event handler. Some sample code in the same Beta 2 documentation uses the code above and it works, at least for GridViews.
At this point, you need to retrieve in the postback some values to identify the corresponding data row--the one whose data you need to process.
- Option #1: You set one or more key fields to be carried on through the DataKeys collection. Since DataKeys supports multiple fields (it is only one with DataGrids), you can see it as a kind of personal cargo collection for you to retrieve quick-to-use data. You simply use the key field(s) if you need to go down to the database to obtain fresh data.
- Option #2: With DataGrids you can try a better approach: get the data item index (not the index of the row in the grid, but the index of the corresponding data item in the data source), get the bound enumerable set of rows, and retrieve the data. This works with GridView if you bind through DataSource, because DataSource can return a non-empty collection of data--typically, a DataView object. If you bind the GridView to a data source control through DataSourceID, it doesn't work because the real set of bound rows are not accessible programmatically.
The code below shows how to retrieve (without running a new query) information about the product in the row where you clicked a Add command button.
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Add"))
{
// Get the index of the clicked row
int index = Convert.ToInt32(e.CommandArgument);
// Add the item to the shopping cart
AddToShoppingCart(index);
}
}
private void AddToShoppingCart(int rowIndex)
{
DataKey data = GridView1.DataKeys[rowIndex];
ShoppingItem item = new ShoppingItem();
item.NumberOfItems = 1;
item.ProductID = (int) data.Values["productid"];
item.ProductName = data.Values["productname"].ToString();
item.UnitPrice = (decimal) data.Values["unitprice"];
MyShoppingCart.Add(item);
ShoppingCartGrid.DataSource = MyShoppingCart;
ShoppingCartGrid.DataBind();
}
In my sample page, ShoppingCartGrid is a grid I use to show the contents of the shopping cart. It is bound to a custom collection object of type ShoppingCart stored in the Session and retrieved through the public MyShoppingCart. Finally, DataKeys. By design, it returns an array of DataKey objects with as many fields as there are elements in the DataKeyNames property of the GridView. For the code above to work, you need the following GridView declaration:
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" BackColor="white"
DataKeyNames="productid,productname,unitprice"
AutoGenerateColumns="false" AllowPaging="true"
OnRowCommand="GridView1_RowCommand">
<Columns>
<asp:boundfield datafield="productname" headertext="Product" />
<asp:boundfield datafield="quantityperunit" headertext="Packaging" />
<asp:boundfield datafield="unitprice" headertext="Price" DataFormatString="{0:c}" />
<asp:buttonfield buttontype="Button" text="Add" CommandName="Add" />
</Columns>
</asp:GridView>