Adding Button Columns to the DataGrid Web Control

The ButtonColumn class, as its name implies, adds a column to the DataGrid that contains a button in each row. To add a ButtonColumn to a DataGrid, simply add an <asp:ButtonColumn ... /> tag to the Columns tag in the DataGrid.

For example, imagine that we want to use a DataGrid to list the various books in the titles database table. We'd like to present a button along with each book that adds the specified book to the user's shopping cart. To accomplish this, the first thing we need to do is add a ButtonColumn to our DataGrid. Listing 4.2 shows a DataGrid that contains a BoundColumn for the title of the book (line 39), and a ButtonColumn titled "Add to Cart" (line 38).

Listing 4.2 Adding a ButtonColumn Includes a Button on Each Row in the DataGrid
  1: <%@ import Namespace="System.Data" %>   2: <%@ import Namespace="System.Data.SqlClient" %>   3: <script runat="server" language="VB">   4:   5:   Sub Page_Load(sender as Object, e as EventArgs)   6:    If Not Page.IsPostBack then   7:     '1. Create a connection   8:     Const strConnString as String = "server=localhost;uid=sa;pwd=;  database=pubs"   9:     Dim objConn as New SqlConnection(strConnString)  10:  11:     '2. Create a command object for the query  12:     Const strSQL as String = "SELECT * FROM titles"  13:     Dim objCmd as New SqlCommand(strSQL, objConn)  14:  15:     objConn.Open()  'Open the connection  16:  17:     'Finally, specify the DataSource and call DataBind()  18:     dgTitles.DataSource = objCmd.ExecuteReader(CommandBehavior. CloseConnection)  19:     dgTitles.DataBind()  20:  21:     objConn.Close()  'Close the connection  22:    End If  23:   End Sub  24:  25: </script>  26: <form runat="server">  27:  <asp:datagrid  runat="server"  28:    AutoGenerateColumns="False"  29:    Font-Name="Verdana" Width="50%"  30:    HorizontalAlign="Center" ItemStyle-Font-Size="9">  31:  32:   <HeaderStyle BackColor="Navy" ForeColor="White"  33:     HorizontalAlign="Center" Font-Bold="True" />  34:  35:   <AlternatingItemStyle BackColor="#dddddd" />  36:  37:   <Columns>  38:    <asp:ButtonColumn Text="Add to Cart" />  39:    <asp:BoundColumn DataField="title" HeaderText="Title" />  40:   </Columns>  41:  </asp:datagrid>  42: </form> 

Before we examine the code in detail, first take a moment to examine Figure 4.2, which is a screenshot of Listing 4.2 when viewed through a browser.

Figure 4.2. The ButtonColumn adds a button to each row.

graphics/04fig05.gif

Note that each button is rendered as a hyperlink (see Figure 4.2). This is the default behavior, but you can specify that the ButtonColumn control render its buttons as actual HTML push buttons by setting the ButtonType property of the ButtonColumn control to PushButton. That is, you could alter line 38 as follows:

 <asp:ButtonColumn Text="Add to Cart" ButtonType="PushButton" /> 

Realize that the ButtonType property is purely an aesthetic property regardless of whether you set ButtonType to LinkButton (the default) or PushButton, the semantics of the ButtonColumn will be the same.

Did you notice the Web form in use in Listing 4.2, starting on line 26 with the <form runat="server"> and ending on line 42 with </form>? When using a ButtonColumn within a DataGrid, you must have the DataGrid inside a Web form (also referred to as a "server-side form"). If you fail to do this, your ASP.NET Web page will generate an error when viewed through a browser.

Finally, note that the code in the Page_Load event that retrieves the database information and binds it to the DataGrid is inside an If statement, and is only executed if the page is not being posted back (lines 6 through 22).

Realize that the Web form that surrounds the DataGrid (lines 26 42) is rendered as a standard HTML form whose action parameter is set to the current page's URL. Such a form is called a postback form, because when the form is submitted it posts back to the current page, as opposed to some other Web page.

When the button or hyperlink generated by one of the ButtonColumns is clicked, the Web form is submitted, thereby causing a postback to occur. However, because the Page_Load event handler contains an If statement checking to see whether a postback has occurred (line 6), the DataGrid's DataSource will not be set and the DataBind() method will not be called on the postback.

Initially you might think that this approach would cause the DataGrid to "disappear" upon postback, because on a postback the DataGrid's DataSource is not set and its DataBind() method is not called. However, this is an incorrect assumption. By placing the DataGrid inside of a Web form the contents of the DataGrid are saved to the ASP.NET page's ViewState.

NOTE

The ViewState is a property that all ASP.NET controls have; it is an accumulation of the all the control's property values, and is used to maintain state across postbacks. For more information on the life cycle of ASP.NET Web controls and how information is persisted to the ViewState, be sure to peruse the links in the "On the Web" section at the end of this chapter.


Because the contents of the DataGrid were saved in the ViewState, when the Web page is posted back the contents of the DataGrid can be automatically reconstructed without needing to set the DataSource and call the DataBind() method. This approach has both advantages and disadvantages.

The main disadvantage is that the data in your DataGrid can become stale. Because the DataBind() method is only being called on the first visit to the page and not on subsequent postbacks, if the user makes a number of postbacks, he'll still be viewing the data from when he first visited the page no matter how many times he submits the page. For example, imagine that you have a DataGrid that lists a number of stocks and their current details. These details might include the current bid and ask prices, the stock's daily volume, and so on. Now, imagine that there is a ButtonColumn that has a "Buy This Stock" button next to each stock and its details that causes a postback, running some code that automatically buys the stock for the user and then redisplaying the list of stocks and their details. If you set the DataSource and call the DataBind() method for the DataGrid only on the first visit to the page, the stock details the user will see upon subsequent postbacks will be the stock's details when he first visited the page. Instead, if you reset the DataSource and recall the DataBind method every time the page is posted back, the stock details will be updated to reflect the current information with each purchase.

Although this disadvantage might seem paramount, it is really only a concern for sites working with rapidly changing data. Using a DataGrid to display such data might not be suitable for these sites. Fortunately, the majority of data-driven Web sites use data that changes rather infrequently.

Where the data is relatively stable, there is a performance advantage to not resetting the DataSource and not recalling the DataGrid's DataBind() method on every postback. Chances are the code needed to populate the DataSource, which usually involves database calls or reading XML files, is the slowest component of your ASP.NET Web page. By omitting these calls on each postback, you can expect a small performance gain. (Of course, the gain you'll see depends on how expensive your call is to populate your DataSource.)

The biggest advantage to using the ViewState to store the DataGrid's contents as opposed to simply resetting the DataSource and recalling the DataBind() method is that the state of the DataGrid is maintained. We've yet to explore any examples where the DataGrid's state has come into play, but there are plenty of examples that we'll be investigating in future chapters. For example, in Chapter 8, "Paging the DataGrid Results," we'll see how to add pagination support to the DataGrid. When the user steps from one page to the next, the Web form is posted back. The DataGrid's state, in this scenario, includes what "page" of data is currently being displayed. If the DataSource is reset and the DataBind() method recalled on every postback, this state is lost between postbacks.

Because of these advantages, especially the latter one, it will be increasingly important to place the code that sets the DataGrid's DataSource property and calls its DataBind() method inside an If Not Page.IsPostBack then ... End If block. In fact, in future code examples (starting with Listing 4.3), we will be moving the code that performs these tasks from the Page_Load event handler into a separate function, BindData. This function will then be called from the Page_Load event handler only on the first visit to the page. That is, our code will look like this:

 Sub Page_Load(sender as Object, e as EventArgs)   If Not Page.IsPostBack then    'Call the Sub to handle our DataGrid's data binding    BindData()   End If  End Sub  Sub BindData()   ' Populate a DataSet or DataReader...   ' Set the DataGrid's DataSource property accordingly...   ' Call the DataGrid's DataBind() method...  End Sub 

Using a separate function to handle the DataGrid data binding will prove to be especially useful when we reach Part III, "Advanced Features of the DataGrid Web Control," in which we'll be examining how to page, sort, and edit the DataGrid's data.

You might be wondering when to reset the DataGrid's DataSource property and recall its DataBind() method on postbacks and when not to do this. A general rule of thumb is that if the DataGrid is in a Web form and you have any controls on the page that will be posting back (such as ButtonColumns in the DataGrid or generic Buttons somewhere within the Web form), you should only set the DataSource property and call the DataBind() method on the first page load, and not on the postbacks.

Responding to a User Clicking a ButtonColumn

Recall that when a button or hyperlink generated by a ButtonColumn control is clicked, the Web form is submitted, causing a postback to the ASP.NET Web page. When the postback occurs, the DataGrid control's ItemCommand event is fired. Hence, to have server-side code execute when a ButtonColumn is clicked, you simply need to add an event handler for the DataGrid's ItemCommand event.

The event handler for the DataGrid's ItemCommand event must have the following definition:

 ' In VB.NET  Public Sub EventHandlerName(sender as Object, e as DataGridCommandEventArgs)  // In C#  public void EventHandlerName(object sender, DataGridCommandEventArgs e) 

In addition to providing an event handler, you must specify that the DataGrid's ItemCommand should be handled by your event handler. To accomplish this, simply add OnItemCommand="EventHandlerName " to the DataGrid declaration, as seen in Listing 4.3.

CAUTION

Imagine that you have set up your DataGrid with a ButtonColumn button and have written an ItemCommand event handler that performs some action. In testing the ASP.NET page, you first load it into your browser with no errors reported. Next, you click on one of the buttons the page is posted back, but the action you expected when the ButtonColumn button was clicked fails to happen. Argh!

You might find yourself in this situation, banging your head against the wall for hours trying to figure out what's wrong. Be sure to take a moment to ensure that the ItemCommand event handler is wired up to the DataGrid's ItemCommand event! If you leave out OnItemCommand="EventHandlerName " from the DataGrid's declaration, the ItemCommand event handler won't fire, so your ButtonColumn buttons or hyperlinks will seem lifeless, never performing the actions you expect.


Let's take a moment to create a simple event handler, wiring it up to the DataGrid's ItemCommand event. Building on the code from Listing 4.2, we simply need to add an event handler in the server-side script block (or add it to the code-behind class, if you're using the code-behind technique) and specify in the DataGrid declaration that when the ItemCommand event is fired, the appropriate event handler should be called. Listing 4.3 contains these needed additions.

Listing 4.3 The DataGrid's ItemCommand Event Fires When the User Clicks the ButtonColumn
  1: <%@ import Namespace="System.Data" %>   2: <%@ import Namespace="System.Data.SqlClient" %>   3: <script runat="server">   4:   5:   Sub Page_Load(sender as Object, e as EventArgs)   6:    If Not Page.IsPostBack then   7:     BindData()   8:    End If   9:   End Sub  10:  11:  12:   Sub BindData()  13:    '1. Create a connection  14:    Const strConnString as String = "server=localhost;uid=sa;pwd=; database=pubs"  15:    Dim objConn as New SqlConnection(strConnString)  16:  17:    '2. Create a command object for the query  18:    Const strSQL as String = "SELECT * FROM titles"  19:    Dim objCmd as New SqlCommand(strSQL, objConn)  20:  21:    objConn.Open()  'Open the connection  22:  23:    'Finally, specify the DataSource and call DataBind()  24:    dgTitles.DataSource = objCmd.ExecuteReader(CommandBehavior.CloseConnection)  25:    dgTitles.DataBind()  26:  27:    objConn.Close()  'Close the connection  28:   End Sub  29:  30:  31:   Sub dgTitles_ItemCommand(sender as Object, e as DataGridCommandEventArgs)  32:    Response.Write("A ButtonColumn has been clicked!")  33:   End Sub  34:   35: </script>  36:  37: <form runat="server">  38:  <asp:datagrid  runat="server"  39:    AutoGenerateColumns="False"  40:    Font-Name="Verdana" Width="50%"  41:    HorizontalAlign="Center" ItemStyle-Font-Size="9"  42:    OnItemCommand="dgTitles_ItemCommand">  43:  44:   <HeaderStyle BackColor="Navy" ForeColor="White"  45:     HorizontalAlign="Center" Font-Bold="True" />  46:  47:   <AlternatingItemStyle BackColor="#dddddd" />  48:  49:   <Columns>  50:    <asp:ButtonColumn Text="Add to Cart" />  51:    <asp:BoundColumn DataField="title" HeaderText="Title" />  52:   </Columns>  53:  </asp:datagrid>  54: </form> 

First note that we moved the code that sets the DataGrid's DataSource property and calls its DataBind() method from the Page_Load event handler (lines 5 through 9) into a separate function, BindData() (lines 12 through 28). Future code examples will use this style to separate the DataGrid data binding from the Page_Load event handler.

Next take a moment to examine the dgTitles_ItemCommand event handler spanning lines 31 to 33. This very simple event handler merely uses the Response.Write method to output "A ButtonColumn has been clicked!" On line 42 we tie this event handler to the DataGrid's ItemCommand event by simply adding OnItemCommand="dgTitles_ItemCommand" to the DataGrid's declaration. With these two additions the event handler and wiring up the event handler to the DataGrid's ItemCommand event handler we now have an action occur when a ButtonColumn is clicked. Figure 4.3 depicts a screenshot after one of the ButtonColumn hyperlinks has been clicked.

Figure 4.3. The message "A ButtonColumn has been clicked!" is displayed.

graphics/04fig06.gif

Determining What ButtonColumn Row Has Been Clicked

Because a ButtonColumn adds a hyperlink or button to each row in the DataGrid, it is natural to associate the ButtonColumn in a particular row with the data displayed in the same row. For example, in Figure 4.3 the user is presented with a list of books with a "Add to Cart" hyperlink next to each title. Clearly, the user will assume that clicking the Add to Cart hyperlink next to the The Busy Executive's Database Guide title will add that book to their shopping cart.

Recall that when any ButtonColumn button or hyperlink is clicked, the DataGrid's ItemCommand event is fired. Of course, we will have as many ButtonColumn buttons or hyperlinks as there are rows in the DataGrid, so it's important to be able to determine which of the ButtonColumns was indeed clicked.

As we saw in Chapter 2, "Binding Data to the Data Controls," each row of the DataGrid is represented by a DataGridItem instance. When a ButtonColumn button or hyperlink is clicked, the DataGridItem that contains the clicked button or hyperlink is passed to the ItemCommand event handler as the Item property of the DataGridCommandEventArgs parameter.

The following is the DataGrid's ItemCommand event handler from Listing 4.3 (lines 31 33):

 Sub dgTitles_ItemCommand(sender as Object, e as DataGridCommandEventArgs)    Response.Write("A ButtonColumn has been clicked!")  End Sub 

Note that the second parameter to the event handler is of type DataGridCommandEventArgs, and has been named e. Hence, we can programmatically access the DataGridItem that contains the ButtonColumn that was clicked by using e.Item.

The DataGridItem class contains a number of properties that describe the contents and appearance of the row in the DataGrid. There are a number of display properties, such as BackColor, BorderColor, HorizontalAlign, VerticalAlign, and Width. The property you'll find yourself using most often is the Cells property, which is a collection of TableCell objects. Each TableCell object represents a column in the particular row represented by the DataGridItem.

Returning to our book example from Listing 4.3, we can amend our event handler (dgTitles_ItemCommand) to emit the title of the book whose ButtonColumn hyperlink was clicked, changing the event handler in Listing 4.3 to the event handler shown in Listing 4.4:

Listing 4.4 The Event Handler Now Displays the Title of the Book Whose ButtonColumn Hyperlink Was Clicked
 1: Sub dgTitles_ItemCommand(sender as Object, e as DataGridCommandEventArgs)  2:  Dim strTitle as String  3:  strTitle = e.Item.Cells(1).Text  4:  Response.Write("Adding <i>" & strTitle & "</i> to your shopping cart")  5: End Sub 

To determine the book's title, we referenced the second item in the Cells collection, which represents the second column in the DataGrid (line 3). (Because all collections in .NET are zero-based, Cells(1) returns the second TableCell, not the first.) The reason we accessed the second DataGrid column as opposed to the first is because the first column is the ButtonColumn it's the second column where the title of the book is displayed.

After getting the second TableCell (e.Item.Cells(1)), we access the Text property of the TableCell, which returns the title of the book (line 3). Finally, on line 4 we emit a short message using Response.Write, informing the user of the title he added to his shopping cart. You'll notice that the actual code to add an item to the user's shopping cart is missing. Because this topic is far beyond the scope of this book, I'll just mention that such logic would appear after line 4 and leave it at that.

Determining Which ButtonColumn Column Has Been Clicked

As we have seen in previous DataGrid examples, a DataGrid can contain multiple BoundColumn controls. It should come as no surprise, then, that a DataGrid can also contain multiple ButtonColumn controls. Let's look at an example that includes a DataGrid with more than one ButtonColumn, paying attention to the issues that arise when using multiple ButtonColumns.

Imagine that we have a shopping cart that lists the name and price of the products that the user has currently placed in the cart. Alongside each item are two buttons: a Remove button that removes the selected item from the cart, and a Details button that displays details about the particular item. For this example, let's keep it simple: the "Details" will simply be a textual description.

For this example we'll assume that there is some function called ItemsInCart(), which returns an object that implements an IEnumerable that has a row for each item in the shopping cart and exposes the following fields for the particular product: the ProductID, the name, and the price. (Recall from Chapter 2 that the DataSource can be any object that implements IEnumerable. It might help to just think that the ItemsInCart() method returns either a DataSet or DataReader of some kind.) The ProductID is an integer value that uniquely identifies each product available in our online store. Furthermore, let's assume that there's also a function called GetProductDescription(ID ), which returns the description for the product specified by the input parameter ID . Finally, there will need to be a RemoveItemFromCart(ID ) function that removes a selected product from the user's shopping cart.

Listing 4.5 contains our first attempt at this example. Note that Listing 4.5 does not contain the ItemsInCart(), GetProductDescription(ID ), or RemoveItemFromCart(ID ) functions. This is for three reasons: first, their details are irrelevant; second, they have been removed for brevity; and lastly, in a real-world project they would likely be methods in a .NET component, meaning that these functions would not appear in the ASP.NET Web page.

Listing 4.5 A DataGrid Is Used to Represent a Shopping Cart
  1: <%@ import Namespace="System.Data" %>   2: <%@ import Namespace="System.Data.SqlClient" %>   3: <script runat="server" language="C#">   4:  void Page_Load(object sender, EventArgs e)   5:  {   6:   if (!Page.IsPostBack)   7:    BindData();   8:  }   9:  10:  11:  void BindData()  12:  {  13:   dgCart.DataSource = ItemsInCart();  14:   dgCart.DataBind();  15:  }  16:  17:  18:  void dgCart_ItemCommand(object sender, DataGridCommandEventArgs e)  19:  {   20:   Response.Write("A button was clicked...");  21:  }  22: </script>  23:  24: <form runat="server">  25:  <asp:DataGrid runat="server"   26:     AutoGenerateColumns="False"  27:     Font-Name="Verdana"  28:     Font-Size="8pt"  29:     OnItemCommand="dgCart_ItemCommand">  30:  31:   <HeaderStyle HorizontalAlign="Center" Font-Bold="True"  32:     Font-Size="11pt" BackColor="Navy" ForeColor="White" />  33:  34:   <Columns>  35:    <asp:ButtonColumn Text="Remove" ButtonType="PushButton"  36:        HeaderText="Remove" />  37:    <asp:ButtonColumn Text="Details" ButtonType="PushButton"  38:        HeaderText="Details" />  39:  40:    <asp:BoundColumn DataField="Name" HeaderText="Product Name" />  41:    <asp:BoundColumn DataField="Price" HeaderText="Price"  42:        ItemStyle-HorizontalAlign="right"  43:        DataFormatString="{0:c}" />  44:   </Columns>  45:  </asp:DataGrid>  46: </form> 

A screenshot of Listing 4.5 can be seen in Figure 4.4. With the ButtonColumns on lines 35 through 38, we've set the ButtonType property to PushButton, which renders the ButtonColumn as a traditional HTML button (see Figure 4.4). Note that we've also added an event handler for the DataGrid's ItemCommand event (dgCart_ItemCommand, lines 18 21).

Figure 4.4. Each item in the shopping cart has two buttons.

graphics/04fig07.gif

Take a moment to inspect the code and try to ascertain whether Listing 4.5 will work. Hopefully you were able to identify the following fault: the DataGrid has only one ItemCommand event, which is raised when any ButtonColumn button or hyperlink is clicked. For instance, suppose that the Remove button for Item 1 is clicked. Although we can determine the precise row that had a button clicked (via e.Item in the dgCart_ItemCommand event handler), we cannot programmatically determine whether it was the Remove button or the Details button that was clicked. This is because both ButtonColumn buttons (Remove and Details) both raise the same DataGrid event, ItemCommand.

To handle this, the ButtonColumn class contains a string property called CommandName. This property can be used to uniquely identify each ButtonColumn in the DataGrid control. For example, in Listing 4.5 we could give each ButtonColumn a CommandName by changing lines 35 through 38 to the following:

 <asp:ButtonColumn Text="Remove" ButtonType="PushButton"      HeaderText="Remove" CommandName="RemoveButton" />  <asp:ButtonColumn Text="Details" ButtonType="PushButton"      HeaderText="Details" CommandName="DetailsButton" /> 

From the ItemCommand event handler, we can determine what ButtonColumn was clicked by checking the e.CommandName property (where e is the DataGridCommandEventArgs parameter of the ItemCommand event handler). Given this, our dgCart_ItemCommand event handler would be changed to the following:

 void dgCart_ItemCommand(object sender, DataGridCommandEventArgs e)  {   if (e.CommandName == "RemoveButton")   {    // the Remove button has been clicked...   }   if (e.CommandName == "DetailsButton")   {    // the Details button has been clicked...   }  } 

All that remains now is to write the code to remove an item from the shopping cart and provide details about a particular item from the shopping cart. At first glance, this might seem quite trivial after all, we have the GetProductDescription(ID ) and RemoveItemFromCart(ID ) functions to aid us. Realize however that these two functions require a ProductID as the input parameter. Hence, we need some way to determine the ProductID of the item whose Remove or Details button was clicked.

There are two ways to accomplish this:

  1. Use a hidden BoundColumn in the DataGrid, and then reference the Text property of the TableCell representing this BoundColumn in the dgCart_ItemCommand event handler.

  2. Set the DataKeyField property of the DataGrid control to ProductID and use the DataKeys collection in the dgCart_ItemCommand event handler.

Essentially these are two different ways to pass on a primary key for a row in the DataGrid to the event handler, a task that we'll find ourselves doing quite frequently in Part III. In the next two sections we'll examine how to use these two approaches. Be sure to carefully read over these next two sections, as we'll be using the techniques learned here extensively throughout the remainder of this book.

Using a Hidden BoundColumn to Pass Along a Primary Key Field

To use the first method, we need to add a BoundColumn to the DataGrid for the ProductID field from our DataSource. Because we don't want to show the ProductID BoundColumn in our shopping cart, we set the Visible property of the BoundColumn to False. The code following shows the DataGrid declaration with the added BoundColumn (some of the DataGrid's stylistic properties have been omitted for brevity):

 <asp:DataGrid runat="server"      AutoGenerateColumns="False"     OnItemCommand="dgCart_ItemCommand"     ... >   <Columns>    <asp:ButtonColumn Text="Remove" ButtonType="PushButton"        HeaderText="Remove" CommandName="RemoveButton" />    <asp:ButtonColumn Text="Details" ButtonType="PushButton"        HeaderText="Details" CommandName="DetailsButton" />    <asp:BoundColumn DataField="ProductID" Visible="False" />    <asp:BoundColumn DataField="Name" HeaderText="Product Name" />    <asp:BoundColumn DataField="Price" HeaderText="Price"        ItemStyle-HorizontalAlign="right"        DataFormatString="{0:c}" />   </Columns>  </asp:DataGrid> 

The preceding DataGrid will be rendered the same as the DataGrid in Figure 4.4. However, now we can reference the ProductID as the third Cell in the DataGridItem whose ButtonColumn button was clicked in the dgCart_ItemCommand event handler. That is, we can use the following code in the dgCart_ItemCommand event handler to get the ProductID of the product whose associated ButtonColumn button was clicked:

 int selectedProductID = Convert.ToInt32(e.Item.Cells[2].Text); 

Note that we are grabbing the third Cell from the selected DataGridItem using e.Item.Cells[2] remember that the Cells collection is zero-based, so Cells[2] is retrieving the third entry. This returns a TableCell, whose Text property we then read. This gives us the ProductID as a string, but we need it as an integer; hence, the Convert.ToInt32 method is used to convert the string into an integer.

Listing 4.6 contains the complete code for our shopping cart example using the hidden BoundColumn to pass along the primary key. Figure 4.5 shows a screenshot of the code in Listing 4.6 after the user has clicked on the Details button for a particular item.

Figure 4.5. The details are displayed for a particular product.

graphics/04fig01.gif

Listing 4.6 A Hidden BoundColumn Is Used to Pass Along the Primary Key
  1: <%@ import Namespace="System.Data" %>   2: <%@ import Namespace="System.Data.SqlClient" %>   3: <script runat="server" language="C#">   4:   void Page_Load(object sender, EventArgs e)   5:   {   6:    if (!Page.IsPostBack)   7:     BindData();   8:   }   9:  10:  11:   void BindData()  12:   {  13:    dgCart.DataSource = ItemsInCart();  14:    dgCart.DataBind();  15:   }   16:  17:  18:   void dgCart_ItemCommand(object sender, DataGridCommandEventArgs e)  19:   {  20:    // Get the ProductID of the selected row.  21:    int selectedProductID = Convert.ToInt32(e.Item.Cells[2].Text);  22:  23:    if (e.CommandName == "RemoveButton")  24:    {  25:     // Remove the Product from the Cart and rebind the DataGrid  26:     RemoveItemFromCart(selectedProductID);  27:     BindData();  28:    }  29:    if (e.CommandName == "DetailsButton")  30:    {  31:     // Display the Product Details  32:     lblProductDetails.Text = GetProductDescription(selectedProductID);  33:    }  34:   }  35: </script>  36: <form runat="server">  37:  <p>  38:  <asp:label runat="server"   39:     Font-Name="Verdana" Font-Size="11pt" />  40:  </p>  41:  42:  <asp:DataGrid runat="server"   43:     AutoGenerateColumns="False"  44:     Font-Name="Verdana" Font-Size="8pt"  45:     OnItemCommand="dgCart_ItemCommand">  46:  47:   <HeaderStyle HorizontalAlign="Center" Font-Bold="True"  48:     Font-Size="11pt" BackColor="Navy" ForeColor="White" />  49:  50:   <Columns>  51:    <asp:ButtonColumn Text="Remove" ButtonType="PushButton"  52:        HeaderText="Remove" CommandName="RemoveButton" />  53:    <asp:ButtonColumn Text="Details" ButtonType="PushButton"  54:        HeaderText="Details" CommandName="DetailsButton" />  55:  56:    <asp:BoundColumn DataField="ProductID" Visible="False" />  57:    <asp:BoundColumn DataField="Name" HeaderText="Product Name" />  58:    <asp:BoundColumn DataField="Price" HeaderText="Price"   59:        ItemStyle-HorizontalAlign="right"  60:        DataFormatString="{0:c}" />  61:   </Columns>  62:  </asp:DataGrid>  63: </form> 

The first thing to note in Listing 4.6 is the addition of a Label control, lblProductDetails (lines 38 and 39). When the Details button is clicked, this Label control is used to display the information about the particular product.

Next, take a moment to examine the dgCart_ItemCommand event handler. On line 21 the selectedProductID variable is set to the Text of the hidden ProductID BoundColumn. Lines 25 through 27 are executed if the Remove button was clicked. On line 26 the RemoveItemFromCart(ID ) function is called, passing in the selectedProductID. Following that, on line 27 the BindData() function is called, repopulating the DataSource and rebinding the DataGrid. If line 27 is omitted, and the call to BindData() is not made, then upon postback the DataGrid would contain the same rows it contained prior to the user clicking the Remove button.

If the user clicked the Details button for a particular item, lines 31 and 32 are executed. On line 32 the lblProductDetails Label control's Text property is set to the string value returned by the GetProductDescription(ID ) function. Again, we pass in the selectedProductID.

Using the DataKeyField Property and the DataKeys Collection to Pass Along a Primary Key Field

Another, more natural way to pass along primary key field information through a DataGrid is to use the DataKeyField property. The DataKeyField property is a string property of the DataGrid class. Simply set this property equal to the name of the primary key field in the DataSource. Returning to our example from Listing 4.5, to use the DataKeyField property approach, the DataGrid declaration would be changed to

 <asp:DataGrid runat="server"      AutoGenerateColumns="False"     Font-Name="Verdana"     Font-Size="8pt"     OnItemCommand="dgCart_ItemCommand"     DataKeyField="ProductID">   <Columns>    ...   </Columns>  </asp:DataGrid> 

By setting the DataKeyField property, we can now access the DataGrid's DataKeys property. This property is a collection and contains an entry for each row in the DataSource. The value of each entry is the value of the specified DataKeyField. That is, the first (0th) element in the DataKeys collection is equal to the value of the ProductID for the first row in the DataGrid, the second (1st) element in the DataKeys collection is equal to the value of the ProductID for the second row in the DataGrid, and so on.

Given this fact, all we need to do to set the selectedProductID variable in our dgCart_ItemCommand event handler is set it to the proper item of the DataKeys collection. The proper DataKeys item has the same index as the DataGrid row whose ButtonColumn was clicked. Fortunately, the DataGridItem class contains an ItemIndex property that returns the row's index in the DataGrid (again, like .NET collections, this is zero-based). Hence, the following code will successfully return the ProductID of the shopping cart item whose ButtonColumn button was clicked, assigning the result to the selectedProductID variable:

 int selectedProductID = (int) dgCart.DataKeys[e.Item.ItemIndex]; 

Listing 4.7 contains source code to use the DataKeyField and DataKeys properties to pass along primary key field information. Note that aesthetically and semantically the output from Listing 4.7 is identical to that of Listing 4.6; therefore, to see a screenshot of the code in Listing 4.6, simply refer back to Figure 4.5.

Listing 4.7 The DataKeyField and DataKeys Properties Are Used to Pass Along Primary Key Information
  1: <%@ import Namespace="System.Data" %>   2: <%@ import Namespace="System.Data.SqlClient" %>   3: <script runat="server">   4:   void Page_Load(object sender, EventArgs e)   5:   {   6:    if (!Page.IsPostBack)   7:     BindData();   8:   }   9:  10:  11:   void BindData()  12:   {  13:    dgCart.DataSource = ItemsInCart();  14:    dgCart.DataBind();  15:   }  16:  17:  18:   void dgCart_ItemCommand(object sender, DataGridCommandEventArgs e)  19:   {  20:    // Get the ProductID of the selected row.  21:    int selectedProductID = (int) dgCart.DataKeys[e.Item.ItemIndex];  22:  23:    if (e.CommandName == "RemoveButton")  24:    {  25:     // Remove the Product from the Cart and rebind the DataGrid  26:     RemoveItemFromCart(selectedProductID);  27:     BindData();  28:    }  29:    if (e.CommandName == "DetailsButton")  30:    {  31:     // Display the Product Details  32:     lblProductDetails.Text = GetProductDescription(selectedProductID);  33:    }  34:   }  35: </script>  36: <form runat="server">  37:  <p>   38:  <asp:label runat="server"   39:     Font-Name="Verdana" Font-Size="11pt" />  40:  </p>  41:  42:  <asp:DataGrid runat="server"   43:     AutoGenerateColumns="False"  44:     Font-Name="Verdana" Font-Size="8pt"  45:     OnItemCommand="dgCart_ItemCommand"  46:     DataKeyField="ProductID">  47:  48:   <HeaderStyle HorizontalAlign="Center" Font-Bold="True"  49:     Font-Size="11pt" BackColor="Navy" ForeColor="White" />  50:  51:   <Columns>  52:    <asp:ButtonColumn Text="Remove" ButtonType="PushButton"  53:        HeaderText="Remove" CommandName="RemoveButton" />  54:    <asp:ButtonColumn Text="Details" ButtonType="PushButton"  55:        HeaderText="Details" CommandName="DetailsButton" />  56:  57:    <asp:BoundColumn DataField="Name" HeaderText="Product Name" />  58:    <asp:BoundColumn DataField="Price" HeaderText="Price"  59:        ItemStyle-HorizontalAlign="right"  60:        DataFormatString="{0:c}" />  61:   </Columns>  62:  </asp:DataGrid>  63: </form> 

The key pieces to Listing 4.7 include setting the DataKeyField property to the primary key field (ProductID, line 46), and then determining the ProductID of the row whose ButtonColumn button was clicked via the DataKeys collection (line 21). Other than line 21, the dgCart_ItemCommand event handler in Listing 4.7 is identical to the event handler in Listing 4.6.

What Method Is Best for Passing Along Primary Key Information?

In comparing the two methods for passing along primary key information, you'll probably agree with me that the latter approach using the DataKeyField and DataKeys properties is more straightforward than using hidden BoundColumns. The shortcoming with the DataKeyField/DataKeys approach is that it can only work with data that has one primary key column. If you are displaying data in a DataGrid that has a composite primary key (that is, a primary key that is composed of two or more fields), and you need to reference the rows by the composite primary key in some fashion, then you will have no choice but to use multiple hidden BoundColumns.

Hence, I recommend using the DataKeyField and DataKeys properties for simple primary key fields, and only resorting to hidden BoundColumns for more complex scenarios.

NOTE

As we'll see in the next chapter, the DataList also supports the DataKeyField and DataKeys properties. This is yet another reason to become comfortable using this approach in passing along primary key information.


Common Uses for ButtonColumns

As we saw with the examples in the past few sections, ButtonColumns have a number of real-world purposes in a DataGrid. ButtonColumns are excellent candidates when you need to enable the user to modify the state of a DataGrid in some way. As we saw in Listings 4.5, 4.6, and 4.7, a ButtonColumn was used for a Remove button, which updated the state of the DataGrid by removing an item from the DataGrid's DataSource and then rebinding the DataGrid.

ButtonColumns are also great for having some action occur on the same page that the DataGrid exists on. Imagine that you have a list of stocks in a DataGrid and alongside each stock you have a Current Quote button. As we already know, when clicked this button would cause a postback to the ASP.NET Web page, firing the DataGrid's ItemCommand event. In our event handler, we could make a call to our data store, obtaining the current quote information for the selected stock. This information could then be displayed in a Label control on the same page as the DataGrid that lists the various stocks. For an example of this technique, be sure to read "An Extensive Examination of the DataGrid Web Control: Part 3," which is listed in the "On the Web" section at the end of this chapter.

ButtonColumns can also be used as simple redirects. For example, you might have a page that displays a list of products, and next to each product you want a link that will lead the user to a separate ASP.NET Web page, passing along the product's unique ID in the QueryString. This separate ASP.NET page would read in the product's unique ID from the QueryString and display the details about the specified item.

To accomplish this functionality, we could use a ButtonColumn hyperlink, and simply issue a Response.Redirect to the product details page in the ItemCommand event handler, passing along the ProductID in the QueryString. However, as we'll see in the next section, such functionality can probably be added by using the HyperLinkColumn, one of the other built-in DataGrid column types. Unfortunately the HyperLinkColumn has its limitations, as we'll see shortly, and for certain situations it might be ideal to use the ButtonColumn for a redirect.



ASP. NET Data Web Controls Kick Start
ASP.NET Data Web Controls Kick Start
ISBN: 0672325012
EAN: 2147483647
Year: 2002
Pages: 111

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net