I've been messing with cascading drop-down lists in the FormView control. In a comment, someone asked if the same thing can be done in a GridView control in edit mode. Yes. The overall approach is pretty much the same. There are a few details that differ because we're in a GridView row.
To review, when you're working with cascading DropDownList controls, you need to perform these tasks:- Populate each of the DropDownList controls.
- Pre-select the appropriate value in each list to match what's currently in the data.
- When the user selects a new value in the master dropdown, repopulate the detail dropdown with a new set of dependent values.
- When the row is saved, read the values from the dropdown lists and use them to set the values of parameters that will be passed to the database query that updates the database.
Update Some folks have requested info about the schema of the tables involved in this exercise. Here you go:
Customers CustomerID Name Manufacturer Model | Manufacturers Manufacturers | Models ModelID Manufacturer Model |
As I say, you can do this in the GridView control in a way similar, but not identical, to how you do it in FormView. Let's say you have a grid that looks like this in normal mode (ItemTemplate):
And like this in edit mode (EditItemTemplate):
When you configure the GridView control, you have to convert the columns for Manufacturer
and Model
from BoundField columns (default) to TemplateField columns. A template column gives you a way to design the column contents freeform. Then you edit the columns. For the ItemTemplate of both columns, you add a Label control and data-bind it using the Bind (or even just the Eval) method. Here's an example of the Manufacturer
column showing just the ItemTemplate element:<asp:TemplateField HeaderText="Manufacturer"
SortExpression="Manufacturer">
<ItemTemplate>
<asp:Label ID="Label1"
runat="server"
Text='<%# Bind("Manufacturer") %>'>
</asp:Label>
</ItemTemplate>
[...]
The EditItemTemplate is where the fun happens. As with the FormView example from before, the template contains both the drop-down list and the data source control for that list. Here's the markup for the EditItemTemplate element for the Manufacturer
column:<EditItemTemplate>
<asp:DropDownList
ID="listManufacturers"
runat="server"
DataSourceID="dsManufacturers"
DataTextField="Manufacturer"
DataValueField="ManufacturerID"
autopostback=true
OnSelectedIndexChanged="listManufacturers_SelectedIndexChanged">
</asp:DropDownList>
<asp:AccessDataSource ID="dsManufacturers"
runat="server"
DataFile="~/App_Data/Cars.mdb"
SelectCommand="SELECT [ManufacturerID], [Manufacturer] FROM [Manufacturers]">
</asp:AccessDataSource>
</EditItemTemplate>
Notice that the AutoPostBack property of the DropDownList control is true. As soon as users select an item from the list, we want to refresh the dependent drop-down list.
Here's the Model
column (I wrapped the query for readability):<asp:TemplateField HeaderText="Model" SortExpression="ModelName">
<ItemTemplate>
<asp:Label ID="Label2"
runat="server"
Text='<%# Bind("ModelName") %>'>
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList ID="listModels"
runat="server"
DataSourceID="dsModelsByManufacturer"
DataTextField="ModelName"
DataValueField="ModelID" />
<asp:AccessDataSource
ID="dsModelsByManufacturer"
runat="server"
DataFile="~/App_Data/Cars.mdb"
SelectCommand="SELECT [ModelID], [ModelName] FROM [Models] WHERE
([ManufacturerID] = ?)">
<SelectParameters>
<asp:Parameter Name="ManufacturerID" />
</SelectParameters>
</asp:AccessDataSource>
</EditItemTemplate>
</asp:TemplateField>
The Model
column also contains a data source control. The query is parameterized; the ManufacturerID
value will be supplied at run time.
And now some code. First, we want to populate the controls and preselect the current item. The right time to do that is when the GridView control is doing its data-binding thing.Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e _
As System.Web.UI.WebControls.GridViewRowEventArgs)
If (e.Row.RowState And DataControlRowState.Edit) = _
DataControlRowState.Edit Then
Dim dv As System.Data.DataRowView = e.Row.DataItem
' Preselect correct value in Manufacturers list
Dim listManufacturers As DropDownList = _
e.Row.FindControl("listManufacturers")
listManufacturers.SelectedValue = dv("ManufacturerID")
' Databind list of models in dependent drop-down list
Dim listModels As DropDownList = _
e.Row.FindControl("listModels")
Dim dsc As AccessDataSource = _
e.Row.FindControl("dsModelsByManufacturer")
dsc.SelectParameters("ManufacturerID").DefaultValue = _
dv("ManufacturerID")
listModels.DataBind()
listModels.SelectedValue = dv("ModelID")
End If
End Sub
(Some ugly line-continuations in there, sorry.)
The drop-down lists are displayed only in edit mode, so the code first tests e.Row.RowState. That guy threw me for a while -- turns out that it's a bitfield, so you test it by ANDing it. Having established that edit mode is active, the code gets the current manufacturer ID from the current data item (typed as DataRowView) and selects that value. Then it uses the manufacturer ID to set the parameter value for the query in the model data source, binds the model drop-down list, and then preselects the current model.
Wow, this is getting long. To be continued ...
Update The next blog entry finishes this discussion and includes a link to the source code for this page.