Friday, July 13, 2007

Using multiple DropDownLists in a DataGrid



In this article, I'll demonstrate how to display and update multiple master records in a DataGrid, where each row contains a related DropDownList. The driving factor behind this article was the need to update multiple event records at the same time using a DropDownList containing all the available status codes for each event. The standard DataGrid method of providing an Edit button for each row was inefficient for the application, and after searching the usual code sites for examples, I developed the essence of the solution detailed here. I'm using the Northwind database to demonstrate the technique.

Displaying the Records

Figure 1 shows five records from the Products table in the Northwind database with the DropDownList populated with the items in the Categories table. The category that is currently assigned to each product is the 'selected' item in the DropDownList.
Figure 1

Populating the DataGrid

The DataGrid is constructed using the generic TemplateColumn and ItemTemplate syntax, in the aspx page.
<asp:datagrid id="dgProducts" runat="server" 
OnItemDataBound="dgProducts_ItemDataBound" AutoGenerateColumns="False">
<asp:TemplateColumn HeaderText="<b>Product Name">
<asp:Label runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "ProductName") %>'

<asp:TemplateColumn HeaderText="<b>Categories">
<asp:DropDownList runat="server" ID="productcategories"></asp:DropDownList>

<asp:TemplateColumn HeaderText="keys" Visible="False">
<asp:Label runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "ProductID") +
"|" + DataBinder.Eval(Container.DataItem,
"CategoryID") %>' ID="RecordKeys" Visible="False"/>
The DropDownList column is populated using the code in the dgProducts_ItemDataBound method, which is an event fired for each row in the DataGrid. Specify which method to fire on the OnItemDataBound attribute of the DataGrid (dgProducts in this example). The row is searched until the DropDownList control 'productcategories' is located. Once the control is obtained, it is set to the Categories dataset, 'dsCategories', that was retrieved earlier (see the source code included with this article). The selected item is then set using the CategoryID retrieved from the Products table.
public void dgProducts_ItemDataBound(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
int ColumnForCategoryID = 2; //Used to locate the data element we want

if ((e.Item.ItemType == ListItemType.Item) ||
(e.Item.ItemType == ListItemType.AlternatingItem))
//It is the type of item we want to process.
//Now spin through the controls for this item.
for (int i = 0; i < e.Item.Controls.Count; i++)
//For this demo we only care about the one DropDownList that is on the row
if (e.Item.Controls[i].Controls[1].GetType().ToString() ==
System.Web.UI.WebControls.DropDownList ddl =
//Make sure the DropDownList is the one we want.
//The name corresponds to the ID used
//on the asp:DropDownList in the ItemTemplate
if (ddl.ID.ToString().ToLower().Equals("productcategories"))
//Build the DropDownList with the categories
//that we obtained in the Page_Load method
ddl.DataSource = dsCategories;
ddl.DataTextField = "CategoryName";
ddl.DataValueField = "CategoryID";
//Set the selected item in the DropDownList to the category that is
//actually the one currently assigned for this particular product.
//(See the SQL statement in the GetProductInfo
//method to see what columns are being retrieved.)
SetStatusDDLSelection(ddl, ((DataRowView)
} //ends productcategories if
} //ends DropDownList if
} //ends try
catch (Exception ex)
//Do something better here.
lblMessageOut.Text += "<br>error=" + ex.Message + "<br>";
} //ends catch
} //ends Controls.Count spin
} //ends ItemType if

} //ends dgProducts_ItemDataBound
See the Microsoft .NET Framework Class Library help system for additional information on the DataGrid.ItemDataBound event.
The other noteworthy column in the DataGrid is the RecordKeys field. This is a non-visible field in the DataGrid, into which the primary key of the product record (ProductID), as well as the foreign key to the Categories table (CategoryID) of the original category is saved. This provides an easy way to retrieve the database keys for each data row. These keys will be used during the update process to determine if an update to a given record is really needed. Change the Visible="False" attribute on the RecordKeys field in the ItemTemplate to display the record key column. Please note that there are multiple ways to save data on the grid or form, and this is just one!

Changing Selections

As an example, change the category for the Aniseed Syrup product to be Dairy Products, and the Camembert Pierrot product to be Condiments, as shown in Figure 2, before clicking the Update button.
Figure 2
When the page posts back to itself from the button click event, the code in the Page_Load method will flow into the DoTheWork method where the main processing is preformed.

Doing the Work

The purpose of the DoTheWork method is to cycle through the DataGrid, processing each DataGridItem present. For each of the DataGridItem items, the controls of interest: productcategories DropDownList, recordkeys Label, and the productname Label will be obtained. The following code snippet provides a high level picture of the entire method. Each of the primary code areas will be examined separately.
private void DoTheWork()
#region Create the local variables