1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


October 25, 2006  |  Cascading DropDownList controls -- Part 2  |  45292 hit(s)

The other day I was playing around with how to do cascading drop-down lists using only declarative markup. But as I said then, that works only if you have a straightforward scenario, as in two standalone DropDownList controls just somewhere on the page.

Another common situation is having the drop-down lists inside the template of a data-bound control. I showed an example of an all-declarative drop-down list in a FormView control a while back, but that was just a single drop-down inside the template.

What about cascading drop-down lists inside a template? It can be done, but alas, here we must abandon our effort to use only markup. About pure declarative code, the awesome Polita says:
This specific scenario doesn’t quite work correctly in a FormView control because when the DataSource raises its DataSourceChanged event, the second dropdown list’s Eval statement attempts to evaluate against the container’s (FormView’s) databinding context, which is only valid at DataBind time. Since this happens outside of DataBind time, the context isn’t there and the Eval fails. The best way to work around this is to write some code to manually databind either the second (dependent) dropdown list.
Dang. As Polita told me later, "There are lots of ways to do this." So here's one.

I'll again use the Cars.mdb file. The extremely simplistic schema is this:

Customers
CustomerID
Name
Manufacturer
Model
Manufacturers
Manufacturers
Models
ModelID
Manufacturer
Model
We cheat a bit here by not using proper unique IDs and stuff. Bear with me, that's not the interesting part. Here's what we want to accomplish, using a FormView control:



For my example, the FormView control is bound to a data source control for the Customers table; a typical tutorial-type of scenario. The data source control has a Select and an Update command so that we can view and edit data in the FormView control:
<asp:AccessDataSource 
   ID="CustomersDataSource"
   runat="server"
   DataFile="~/App_Data/cars.mdb"
   SelectCommand="SELECT [CustomerID], [Name], [Manufacturer],
   [Model] FROM [Customers]"
   UpdateCommand="UPDATE [Customers] SET [Name] = ?,
   [Manufacturer] = ?, [Model] = ? WHERE [CustomerID] = ?">
   <UpdateParameters>
   <asp:Parameter Name="Name" Type="String" />
   <asp:Parameter Name="Manufacturer" Type="String" />
   <asp:Parameter Name="Model" Type="String" />
   <asp:Parameter Name="CustomerID" Type="Int32" />
   </UpdateParameters>
</asp:AccessDataSource>
Note: Lines are wrapped so they'll fit here.

In the FormView control, the ItemTemplate (read-only) uses Label controls bound to fields in the Customer record. Here's an excerpt:
Name:
<asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>' />
<br />
Manufacturer:
<asp:Label ID="ManufacturerLabel" runat="server" Text='<%# Eval("Manufacturer") %>' />
What we want is cascading drop-down lists in the EditItemTemplate, as we had in the original, all-declarative FormView example. As in that example, we need data source controls inside the template that the drop-down lists can bind to. The drop-down list for Manufacturer can use declarative markup to bind to the data source control and set its SelectedValue property:
Manufacturer: 
<asp:DropDownList
   ID="DropDownList1"
   runat="server"
   AutoPostBack="True"
   DataTextField="Manufacturer"
   DataValueField="Manufacturer"
   SelectedValue='<%# Bind("Manufacturer") %>'
   DataSourceID="ManufacturersDataSource"
   OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" />


<asp:AccessDataSource
   ID="ManufacturersDataSource"
   runat="server"
   DataFile="~/App_Data/cars.mdb"
   SelectCommand="SELECT Manufacturer FROM Manufacturer ORDER BY Manufacturer" />
Note that the AutoPostBack property is set to true; more on that in a sec.

It's the second (dependent) drop-down list (Model) where declarative code falls down. As Polita says, there are various ways to handle this. What I did was to go as far as I could declaratively, which meant adding the DropDownList control and a data source control, as with the first drop-down list. However, I did not attempt to set the second DropDownList control's SelectedValue property (which is the property that would normally use Bind or Eval for data binding.) The Select query for the second data source control is parameterized (where Manufacturer = ?), and there's a <parameter> element, but no value is assigned (yet) to the parameter:
Model:
<asp:DropDownList ID="DropDownList2" runat="server"
DataSourceID="ModelsDataSource"
DataTextField="Model" DataValueField="Model" />


<asp:AccessDataSource ID="ModelsDataSource" runat="server"
DataFile="~/App_Data/cars.mdb"
SelectCommand="SELECT * FROM [Models] where Manufacturer = ?">
<SelectParameters>
<asp:Parameter Name="Manufacturer" />
</SelectParameters>
</asp:AccessDataSource>
We really need code for three tasks:
  • Dynamically get the parameter value for the second data source control (from the selected value of the first drop-down list) and then bind the second drop-down list to that data source.

  • Pass the selected value of the second drop-down list to the FormView control when it's doing an update. (Normally this would be auto-magically handled by a declarative Bind call, but we're not using that for this control).

  • Update the items in the second drop-down list when the first one changes -- the second list, after all, is dependent on the first. This is why we needed to set AutoPostBack to true for the first drop-down list -- so that it will raise an event we can handle to repopulate the second drop-down list.

Here are the corresponding bits of code. First, the code to dynamically get the parameter value and bind the second drop-down list:
Protected Sub FormView1_DataBound(ByVal sender As Object, _
ByVal e As System.EventArgs)
If FormView1.CurrentMode = FormViewMode.Edit Then
Dim dv As System.Data.DataRowView = FormView1.DataItem
Dim ddl2 As DropDownList = FormView1.FindControl("DropDownList2")
Dim dsc As AccessDataSource = FormView1.FindControl("ModelsDataSource")
dsc.SelectParameters("Manufacturer").DefaultValue = dv("Manufacturer")
ddl2.DataBind()
If Not IsDBNull(dv("model")) Then
ddl2.SelectedValue = dv("Model")
End If
End If
End Sub
This is the code to pass the current selection to the update code:
Protected Sub FormView1_ItemUpdating(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.FormViewUpdateEventArgs)
Dim ddl2 As DropDownList = FormView1.FindControl("DropDownList2")
e.NewValues("Model") = ddl2.SelectedValue
End Sub
And finally, the code to repopulate the second list when the first one changes:
Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim dsc As AccessDataSource = FormView1.FindControl("ModelsDataSource")
Dim ddl1 As DropDownList = FormView1.FindControl("DropDownList1")
Dim ddl2 As DropDownList = FormView1.FindControl("DropDownList2")
dsc.SelectParameters(0).DefaultValue = ddl1.SelectedValue
ddl2.DataBind()
End Sub
I wouldn't be surprised to hear that there is a more efficient way to do this, but this seems to work.

You can see the code in action here. And since these little snippets are way out of context, you can see the source code for the entire page here.




Matthew Martin   11 Nov 06 - 7:11 PM

It works! Thanks for writing this. I wondered if you could save some code using data source parameters that used controls as their source. I haven't completely tested it, so I'm not sure myself.

 
mike   11 Nov 06 - 9:59 PM

I'm pretty sure that you can't use a control as the source for data parameters for the second drop-down list. If I understand what you're asking. (?)

 
Eneko Prins   27 Nov 06 - 7:06 AM

Hi! I'm very new in the .Net world so I hope I don't ask anything to obvious. Sorry for any stupid questions in advance.

Cascading or Hierarchical Dropdownlists from within a Formview is exactly what I needed when I run into this post. The only thing is that I use C# in my website and this example is in VB, put I decided to give a go at rewriting it. I'm new in .Net but not in programing in general.

I have run into the following problem:
The AccessDataSource variable dsc is always NULL when asigned to FormView1.FindControl("ModelsDataSource"). In my case, the line of code is:

AccessDataSource vDataSource = (AccessDataSource)FormView1.FindControl("DataSource_Modelos");

This sounds logical to me since the DataSource is not actualy a part of the FormView in my case. It is declared outside the formview, like this:

<asp:FormView ID="FormView1" etc..>
</asp:FormView>

<asp:ObjectDataSource ID="DataSource_Modelos" etc..>
</asp:ObjectDataSource>

If tried obtaining the Datasource form the DropDownList2 which has the Datasource assigned but that didn't work either.

My cuestion is: How can I get the datasource used by the models dropdownlist2?

Kind regards and thanks in advance for any help.




 
limno   24 Jan 07 - 2:04 PM

Hi Mike:
I like your no-code approach. I use your code and tested out a no-code version for these cascading dropdownlists.
I use the following for the second ddl.
<SelectParameters>
<asp:ControlParameter ControlID="FormView1$DropDownList1" Name="Manufacturer" PropertyName="SelectedValue" />
</SelectParameters>

and for the updateparameters:
<asp:ControlParameter ControlID="FormView1$DropDownList1" Name="Manufacturer" PropertyName="SelectedValue" />
<asp:ControlParameter ControlID="FormView1$DropDownList2" Name="Model" PropertyName="SelectedValue" />


 
limno   24 Jan 07 - 9:57 PM

Still need some code for the second ddl.
OnDataBound="DropDownList2Model_DataBound"
protected void DropDownList2Model_DataBound(object sender, EventArgs e)
{
DropDownList DropDownList2Model = (DropDownList)sender;
FormView FormView1 = (FormView)DropDownList2Model.NamingContainer;
if (FormView1.DataItem != null)
{
string strModel = ((DataRowView)FormView1.DataItem)["Model"] as string;
ListItem lm = DropDownList2Model.Items.FindByValue(strModel);
if (lm != null) lm.Selected = true;
}

}


 
Cliff   26 Feb 07 - 8:30 AM

This is very interesting - I have spent ages working on the same problem but inside a GridView. In Edit mode I have a databound DropDownList with a separate DropDownList that acts as a filter to the second (as there are a large number of items to display). Is this possible?
Thanks,
Cliff


 
Vareck   28 Feb 07 - 10:07 AM

Is there a way to use this to insert items.

I have added dropdowns to the insert templates & adjusted the code accordingly.

When ever the mode changes I get an error. Object ref not set to instance of an object.

Any ideas?


 
Payal Vaidya   08 Mar 07 - 2:54 AM

hi,
I want to use cascading controls in formview when mode of opertion is Insert. what kind of changes i have to do in the same code for this. Please help i m new to asp.net 2.0



 
mike   15 Mar 07 - 7:49 AM

@Cliff -- I just posted an example (in two parts) of cascading dropdowns in a GridView control. Payal and Varek, I'll work on Insert next.

 
Ian Herbert   20 Mar 07 - 12:37 PM

Hi Mike
Thanks for writing all this! I'd been struggling for a few days to get some cascading dropdownlists to work in a Details view. Came across your blog - and managed to get everything working in a few minutes (in C#). Fantastic - thanks again.
Ian H


 
Wissam Bishouty   24 Mar 07 - 12:11 AM

Greetings,

thank you for the useful article.

i have a question on the following:

Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim dsc As AccessDataSource = FormView1.FindControl("ModelsDataSource")
Dim ddl1 As DropDownList = FormView1.FindControl("DropDownList1")
Dim ddl2 As DropDownList = FormView1.FindControl("DropDownList2")
dsc.SelectParameters(0).DefaultValue = ddl1.SelectedValue
ddl2.DataBind()
End Sub
when you say for example:
Dim ddl1 As DropDownList = FormView1.FindControl("DropDownList1")
and as we know that you have DropDownList1 in each page of the formview1 control so how you get the dropdownlist1 which is in the edit mode?

your help is highly appreciated
best regards


 
mike   24 Mar 07 - 7:17 AM

@Wissam

Although the FormView control can show multiple pages, only one page is ever instantiated at a time, and the FindControl method is looking for an instance of (for example) DropDownList1 only on the current page.

Is that what your question was?


 
Joel   28 May 07 - 5:29 PM

This is an awesome article. Thanks so much for posting it. I had literally spent days and days searching the web for an elegant solution to this problem. Most of the fixes I found were either poorly explained, or didn't work. I got your solution working in minutes. Any help you can post with Inserts would be greatly appreciated!! Thanks again for sharing this.

 
MorfBoy   30 May 07 - 5:54 AM

Hi mike i'm from venezuela and I ask you something... The cascading work fine in edit mode but i neeeeeed the insert mode and i've many time wait for the insert mode....... Te lo agradeceria en el alma de panita y todo si colocas ese Insert Mode rapido By the way Tambien seria bueno que se trabajara con SQL lo mismo pero en SQL

 
mike   02 Jun 07 - 11:22 AM

I just posted something about Insert mode -- hope it's helpful:

http://mikepope.com/blog/DisplayBlog.aspx?permalink=1756


 
morfboy   15 Jun 07 - 11:27 AM

hi mike thank you very much for you help, the insert and edit formview worked fine.... te quedo vergatario

 
Fleming   29 Jan 08 - 2:47 PM

Fanatastic!! This saved my day.

Thanks a lot!!