tags:

views:

462

answers:

4

Hello,

I'm trying to figure out the best way to achieve this:

I need to show a drop down list when the page loads, the default selected value is nothing, or an empty element (such as "-"). When the user selects one value from the list, another drop down list is added below the first one, and so on.

My problem with this is how you make the page to remember the drop down lists created through postbacks and the values they have selected? Do I have to use some kind of array to store them or something?

Thank you very much.

EDIT: I did this example for dynamically add drop down lists and suscribe them to an event handler but the event won't fire.

EDIT 2: Changed the code because I labelled for VB.Net and posted something in C#. I update the code with some trivial improvements, but still won't fire the event :(

    Private myDdlArray As New List(Of DropDownList)

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    If (Session("ddl") Is Nothing) Then
        Session("ddl") = myDdlArray
    End If

    If (Session("ddlcount") Is Nothing) Then
        Session("ddlcount") = 0
    End If

    myDdlArray = CType(Session("ddl"), List(Of DropDownList))

    Dim myDdl As New DropDownList
    myDdl = New DropDownList
    For Each myDdl In myDdlArray
        panel.Controls.Add(myDdl)
        panel.Controls.Add(New LiteralControl("<br/>"))
    Next

    Session("ddl") = myDdlArray

End Sub

Protected Sub btn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btn.Click

    myDdlArray = CType(Session("ddl"), List(Of DropDownList))
    Dim ddlCount As Integer = CInt(Session("ddlcount")) + 1

    Dim newDdl As New DropDownList
    newDdl.ID = String.Format("ddlNPKMaterial{0}", ddlCount)
    newDdl.AutoPostBack = True

    AddHandler newDdl.SelectedIndexChanged, AddressOf cbo_SelectedIndexChanged

    newDdl.Items.Add("Uno")
    newDdl.Items.Add("Dos")
    newDdl.Items.Add("Tres")

    myDdlArray.Add(newDdl)

    panel.Controls.Clear()

    Dim myDdl As New DropDownList
    myDdl = New DropDownList
    For Each myDdl In myDdlArray
        panel.Controls.Add(myDdl)
        panel.Controls.Add(New LiteralControl("<br/>"))
    Next

    Session("ddl") = myDdlArray
    Session("ddlcount") = ddlCount + 1

End Sub

Protected Sub btnReset_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnReset.Click

    Session("ddl") = Nothing
    Session("ddlcount") = Nothing

End Sub

Protected Sub btnShow_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnShow.Click

    ' Insert brain here

End Sub

Public Sub cbo_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)

    Response.Write(CType(sender, DropDownList).ID)

End Sub

Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender

    If (Session("ddl") Is Nothing) Then
        panel.Controls.Clear()
    End If

End Sub

in the aspx I have:

<form id="form1" runat="server">
<div>
    <asp:Button ID="btn" runat="server" Text="Add" />
    <asp:Button runat="server" ID="btnShow" Text="Tell me" />
    <asp:Button runat="server" ID="btnReset" Text="Reset" />
    <br />
    <asp:Panel runat="server" ID="panel">
        <asp:GridView runat="server" ID="grd">
            <Columns>
                <asp:BoundField HeaderText="Id" DataField="Id" />
            </Columns>
        </asp:GridView>
    </asp:Panel>
</div>
</form>
+1  A: 

You can simply have both dropdown lists exist in your asp code with only one visible on first page load. So something like...

<asp:DropDownList ID="mainDDL" Visible="true" runat="server">
    <asp:ListItem  Text="Elements here" Value="0" />
    <asp:ListItem Text="More elements" Value="1" />
</asp:DropDownList>

<asp:DropDownList ID="dynamicDDL" Visible="false" runat="server">
    <asp:ListItem Text="This is an element of the dynamic DDL" Value="3" />
</asp:DropDownList>

And then when, say the "More Elements" item selected, switch the dynamicDDL's visibility to true.

Then on each postback, on the Page_Load event, check what the value of mainDDL is. If it is 0, set dynamicDDL to have visible=true

Edit:

Okay, I took a stab at this. There is some headway in this, however, and maybe it will lead us to some clues.

To start off, we DO need an array to store this. We will need a static array of DDLs and a static integer to count our elements. These can be defined as...

Private Shared ddl_arr As DropDownList() = New DropDownList(100) {} 'max, 100 ddls.
Private Shared ddl_count As Integer = 0

Now, we'll need a panel to store our DDLs in. This is simple asp scripting, such as...

<asp:Panel ID="parentPanel" runat="server">
    <asp:DropDownList ID="mainDDL" AutoPostBack="true" Visible="true" runat="server">
        <asp:ListItem  Text="Elements here" Value="0" />
        <asp:ListItem Text="More elements" Value="1" />
    </asp:DropDownList>
</asp:Panel>

So now, on our page load we will want to load any of our dropdowns that we have saved so far. This can be coded in a way such as..

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Try
        If TypeOf ddl_arr(0) Is DropDownList Then
            For Each ddl As DropDownList In ddl_arr
                add_ddl(ddl)
            Next
        End If
    Catch ex As Exception ' this is a bad idea, but for brevity..
    End Try
End Sub

Our add_ddl method will simply add our new drop down to the parentPanel.

Protected Sub add_ddl(ByVal ddl As DropDownList)
    Try
        parentPanel.Controls.Add(ddl)
        'add any formatting you would like after each ddl here.
    Catch ex As Exception
    End Try
End Sub

And finally, our method when we change the ddl. This creates a brand new ddl, gives it an id (and whatever properties you may want for it), adds it to the array, increments our counter, and adds it to the page..

Protected Sub mainDDL_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mainDDL.SelectedIndexChanged
    Dim newDDL As DropDownList = New DropDownList()
    newDDL.ID = "ddlID" & ddl_count 'we will need to store a new ID for each one.  So, dynamically generate this.
    'continue to add properties to your ddl in this fashion...
    ddl_arr(ddl_count) = newDDL
    ddl_count = ddl_count + 1
    add_ddl(newDDL)
End Sub

This method should definitely check for the end of the array (among other things), but lets keep things simple. This code will only add a new DDL whenever you change the index of the ORIGINAL DDL. You will need to set each newly created DDL to have a method called (performing the above instructions) whenever the selected index changes for all of those newly crafted DDLs.

Hopefully this gets you in the right direction. This was way less organized than I'd hoped, sorry!

Chris
Thank you Chris, but actually what I meant with "and so on" is that you can have more than two ddl, every time to select one element from the list, another ddl is created, n number of times. Hope I made it clearer.
Sebastian
Hi Sebastian. I've added more. Hopefully this is helpful.
Chris
A: 

Create and add the dropdowns during the Page_Init method and generate the names based on the backing data that you're gathering. When getting input from them, either read their returned values off of the page using the FindControl() method or by getting the returned value of the control from the Request.Form[]. FindControl uses the id of the control, and Requst.Name uses the client id - you can get this information using the same function you use to generate the names.

Overall, ASP.NET isn't very good at working with generated controls, but by keeping the data you're working with in a model or datastructure separate from the controls that are being generated it isn't difficult to both create the controls and retrieve the results.

Dan Monego
I'm running some tests on this and you're damn right, ASP.Net is not good with generated controls!!
Sebastian
+1  A: 

I'll use OnLoad to recreate the controls, but if you're not storing the # of controls in ViewState (you could put it into Session, a HiddenField, Cookie, etc.) then you can do it in OnInit. Either way should still fire the postback event.

<asp:PlaceHolder id="phDdl" runat="server">
    <asp:DropDwonList id="ddl" runat="server" 
     OnSelectedIndexChanged="ddl_SelectedIndexChanged" />
</asp:PlaceHolder>

<script runat="server">
    int DdlIndex {
       get { 
          object o = ViewState["_ddlIndex"];
          if (o == null) {
             ViewState["_ddlIndex"] = 0;
          }
          return (int)ViewState["_ddlIndex"];
       }
       set {
          ViewState["_ddlIndex"] = value;
       }
    }

    protected override void OnLoad(EventArgs e) {
       base.OnLoad(e);
       for (int i = 0; i < DdlIndex; i++) {
          AddDropDownList(i);
       }
    }

    void ddl_SelectedIndexChanged(object sender, EventArgs e) {
       var ddl = (DropDownList)sender;
       if (ddl.SelectedIndex > 0) {
          AddDropDownList(DdlIndex++);
       }
    }

    void AddDropDownList(int i) {
       var ddl = new DropDownList();
       ddl.Id = string.Format("ddl{0}", i);
       ddl.SelectedIndexChanged += ddl_SelectedIndexChanged;
       // add items
       phDdls.Add(ddl);
    }
</script>
Mark Brackett
Adding event handlers to dynamically generated objects stored in a collection is the problem... it won't fire the event!
Sebastian
@Sebastian - It will fire if you follow the rules. Which basically are (a) add before the event would fire, (b) name the control the same thing, and (c) put it in the same place.
Mark Brackett
Oh - and I guess re-attaching the event handler would be (d)....
Mark Brackett
Thanks Mark, good lead, but still no luck... I think I followed the rules you mentioned... didn't I?
Sebastian
No - you're trying to add the same instances of the controls that you used previously (the myDdlArray stored in Session). Frankly, I don't know how/if that would work - though I'd suspect large memory leaks as you're keeping the Page alive. You need to *recreate* the controls, with the same ID - you can't just reuse them.
Mark Brackett
+1  A: 

Dynamic controls are pretty tricky to create. I would start by reading this.

Wyatt Barnett