The markup above is composed of 6 columns of various types of control fields. The first column is a BoundField that holds the ProductOrderID field. You may notice that we set this field as the DataKeyName in the GridView so we can easily reference the ID of the row later on. The 2nd and 3rd columns are TemplateFields that contain a Label control within an <ItemTemplate> and a DropDownList control within an <EditItemTemplate>. This means that on a read-only state the Label will be displayed and on an edit-state the DropDownList will be displayed to allow users to modify the selection of their choice. Note that you need to set AutoPostBack to TRUE for the DropDownList to trigger the SelectedIndexChanged event. The 4th and 5th columns are BoundFields that hold the Price and Quantity fields. You may notice that the Price field has an attribute DataFormatString="{0:c}" set to it. This will transform the value to a currency format when displayed in the browser. Finally, the last column is a CommandField with ShowEditButton enabled. This will generate the Edit link when the GridView is bound to data.
Just for the simplicity of this exercise, I didn't use a database and instead I've just created some classes that will hold some properties on it. Here are the class definitions.
- public class ProductMake
- {
- public int ProductMakeID { get; set; }
- public string Make { get; set; }
- }
- public class ProductModel
- {
- public int ProductModelID { get; set; }
- public int ProductMakeID { get; set; }
- public string Model { get; set; }
- public decimal Price { get; set; }
- }
- public class ProductOrder
- {
- public int ProductOrderID { get; set; }
- public int ProductMakeID { get; set; }
- public int ProductModelID { get; set; }
- public string Make { get; set; }
- public string Model { get; set; }
- public short Quantity { get; set; }
- public decimal Price { get; set; }
- }
Note that this is just an example to make this exercise work. You can always replace this with your DataTable, Entity objects, Custom classes and so on that holds your actual source of data.
The ProductMake class holds two main properties. This is where we store the list of makes of the product. The ProductModel class is where we store all the models for each make. The ProductOrder class is where we store the list of orders the user has chosen. Now let's go ahead and provide this class with some sample data. The following is the code blocks.
The GetUserProductOrder() fetches the list of orders. We will use this as our DataSource in the GridView later. The GetProductMakes() method gets all the available makes that, in this case, we just added 3 main items to. The GetProductModel() method gets all the available models for each make. The GetProductModelByMake() method gets the specific model item and its details are based on the ProductMakeID. This method uses the
LINQ syntax to query the DataSource based on the parameter that was passed to it.
The Implementation
Now it looks as if we already have some sample source of data to work with. Now let's go ahead and do the highlight of this exercise (which is the implementation of the cascading dropdownlist). Here are the code blocks below.
- private void BindGrid()
- {
- gvProducts.DataSource = GetUserProductOrder();
- gvProducts.DataBind();
- }
-
- protected void gvProducts_RowEditing(object sender, GridViewEditEventArgs e) {
- gvProducts.EditIndex = e.NewEditIndex;
- BindGrid();
- }
-
- protected void gvProducts_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e) {
- gvProducts.EditIndex = -1;
- BindGrid();
- }
-
- protected void gvProducts_RowDataBound(object sender, GridViewRowEventArgs e) {
- if (e.Row.RowType == DataControlRowType.DataRow) {
- if ((e.Row.RowState & DataControlRowState.Edit) > 0) {
- DropDownList ddlMake = (DropDownList)e.Row.FindControl("ddlProductMake");
- ddlMake.DataSource = GetProductMakes();
- ddlMake.DataValueField = "ProductMakeID";
- ddlMake.DataTextField = "Make";
- ddlMake.DataBind();
-
- ddlMake.SelectedValue = gvProducts.DataKeys[e.Row.RowIndex].Value.ToString();
-
- DropDownList ddlModel = (DropDownList)e.Row.FindControl("ddlProductModel");
- ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value));
- ddlModel.DataValueField = "ProductModelID";
- ddlModel.DataTextField = "Model";
- ddlModel.DataBind();
-
- ddlModel.SelectedValue = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value))
- .FirstOrDefault().ProductModelID.ToString();
-
- }
- }
- }
-
- protected void ddlProductMake_SelectedIndexChanged(object sender, EventArgs e) {
- DropDownList ddlMake = (DropDownList)sender;
- GridViewRow row = (GridViewRow)ddlMake.NamingContainer;
- if (row != null) {
- if ((row.RowState & DataControlRowState.Edit) > 0) {
- DropDownList ddlModel = (DropDownList)row.FindControl("ddlProductModel");
- ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(ddlMake.SelectedValue));
- ddlModel.DataValueField = "ProductModelID";
- ddlModel.DataTextField = "Model";
- ddlModel.DataBind();
- }
- }
- }
-
- protected void ddlProductModel_SelectedIndexChanged(object sender, EventArgs e) {
- DropDownList ddlModel = (DropDownList)sender;
- GridViewRow row = (GridViewRow)ddlModel.NamingContainer;
- if (row != null) {
- if ((row.RowState & DataControlRowState.Edit) > 0) {
- row.Cells[3].Text = string.Format("{0:C}", GetProductModels()
- .Where(o => o.ProductModelID == Convert.ToInt32(ddlModel.SelectedValue))
- .FirstOrDefault().Price);
- }
- }
- }
The
gvProducts_RowDataBound event is where we bind the DropDownList with the data from our DataSource. First we check the RowType to ensure that we are only manipulating the rows of type DataRow. Please note that a GridView is composed of several row types such as Header, DataRow, EmptyDataRow, Footer, Pager and Separator. The next line in that block is the critical part of the code and that is to determine the Edit state.
Accessing the controls from within <EditItemTemplate> is a bit tricky, especially if you are not really familiar with how the stuff works within a GridView. Equating the RowState to DataControlState.Edit isn't really accurate and you might get an exception when doing so. The
RowState property is a bitwise combination. Therefore, the RowState could indicate that you are in the Edit state and the Alternate state. Hence, you cannot do a simple equality check when you are in Edit mode. Instead you must do something like this.
- if ((e.Row.RowState & DataControlRowState.Edit) > 0)
- {
-
- }
We use the bitwise “&” operator to determine if the GridView is in Edit mode and check the result if its greater than zero. For details about the Bitwise operator see:
Bitwise Operators in C#.
Once we've manage to determine the edit-state then we can now begin accessing the controls using the
FindControl() method and bind it with the corresponding DataSources. If you notice, I've set the SelectedValue for the ddlMake and ddlModel DropDownLists so that by the time the user clicks edit, the DropDownList will be pre-selected with the previous item the user has chosen.
The
ddlProductMake_SelectedIndexChanged event is where we actually do the cascading feature by populating the second DropDownList based from the value selected from the first DropDownList. But before that we need to cast the sender of the object triggering the event to determine which DropDownList is triggered within the GridView row. We then cast the
NamingContainer of the sender to a type of GridViewRow to determine the actual row the user is editing.
The ddlProductModel_SelectedIndexChanged event is where we update the Price value based on the selected Model from the second DropDownList. It basically uses LINQ syntax to query the DataSource and to get the Price based on the selected value of the ddlModel.
Binding the GridView
Finally let's call the BindGrid() method to populate the GridView. Here's the code block below.
- protected void Page_Load(object sender, EventArgs e)
- {
- if (!IsPostBack)
- {
- BindGrid();
- }
- }
The Output
Here are some screenshots from running this code in the page: