I am writing a web page in ASP.Net. Presently, I have an asp:Table that I am using as a sort of "log" for processing output. The idea is that the user selects several files and clicks a button, and each file is "processed" with the log showing what is happening. Processing occurs asynchronously.
Here is the relevant processing segment:
Protected Sub DoAsyncWork()
Dim count = 0
For Each row As GridViewRow In gvList.Rows
count = count + 1
If CType(row.FindControl("cbImport"), System.Web.UI.WebControls.CheckBox).Checked Then
push_to_log("")
push_to_log("Updating Active Projects +" + HttpUtility.HtmlDecode(row.Cells(1).Text).ToString.Substring(0, 30) + "...")
Dim xp(3) As Object
xp(0) = HttpUtility.HtmlDecode(row.Cells(0).Text)
xp(1) = HttpUtility.HtmlDecode(row.Cells(1).Text)
xp(2) = HttpUtility.HtmlDecode(row.Cells(2).Text)
xp(3) = 0
'oDC.UpdateData("Import_P3e_Project ", xp)
If (xp(3) <> 0) Then
push_to_log("Success: " + xp(3).ToString + " have been updated")
Else
push_to_log("Failure: " + xp(3).ToString + " activities updated")
End If
End If
Next
push_to_log("")
push_to_log("Import Complete!")
End Sub
This is how I am calling the process worker function:
Protected Sub button_Import(sender As Object, e As EventArgs) Handles btnImport.Click
Dim t As New Thread(New ThreadStart(AddressOf DoAsyncWork))
t.Priority = Threading.ThreadPriority.Normal
t.Start()
push_to_log("Start Import")
End Sub
The way I am appending things to the log is by dynamically creating rows and cells, then adding them to my table. Here is the relevant subroutine:
Protected Sub push_to_log(ByVal str As String)
Dim newRow As TableRow = New TableRow
Dim newCell As TableCell = New TableCell
logArrayList.Add(str)
Me.ViewState.Add("arrayListInViewState", logArrayList)
newCell.Text = str
newCell.Style("Color") = "White"
newCell.ID = "cell" + (logArrayList.Count - 1).ToString
newRow.ID = "row" + (logArrayList.Count - 1).ToString
newRow.Cells.Add(newCell)
logTable.Rows.Add(newRow)
HiddenButton_Click(HiddenButton, New EventArgs())
'UpdateLogPanel.Update()
'UpdateLogPanel.Focus()
End Sub
I've got the log persisting correctly by using the ViewState to store my arraylist of data and recreating the log on postbacks. The relevant markup for my log looks like this:
<asp:UpdatePanel ID="UpdateLogPanel" UpdateMode="Conditional" runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="HiddenButton" />
</Triggers>
<ContentTemplate>
<div ID="Div1" class="DefinitionPanel" style="text-align:left;height:200px;overflow:hidden;" runat="server">
<span style="display:inline-block; width:100px;"></span>
<div class="scrollingtable">
<div>
<div id="viewContainer">
<asp:table id="logTable" runat="server" enableviewstate="false">
</asp:table>
</div>
</div>
</div>
</div>
<asp:Button ID="HiddenButton" runat="server" style="display:none;" />
</ContentTemplate>
</asp:UpdatePanel>
I am trying to make my asp:Table update every time a message is posted to it. I thought that enabling partial postbacks and using an UpdatePanel would be the correct solution, but my log still does not output anything until the entire process has completed.
After I add a message to my asp:Table/log, I tried calling
UpdateLogPanel.Update()
which didn't seem to make a difference. Finally I tried adding the asp:AsyncPostBackTrigger and hidden button with the hope that it would fix things but it doesn't seem to. Here is what the hiddenButton event looks like:
Protected Sub HiddenButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles HiddenButton.Click
UpdateLogPanel.Visible = True
End Sub
Any guidance on how to make my log re-render itself when I add a message to it would be highly regarded.
I chose to implement using pooling instead of an asynchronous method.
I still use an asp:UpdatePanel. Instead of a table inside of the UpdatePanel, I am now using a gridview, and a timer. When the timer fires, I rebind the gridview, thereby showing any new contents
The key components to making this work:
Square things away with the parent update panel and master page:
Private Sub Page_Init(sender As Object, e As System.EventArgs) Handles Me.Init
'set reference to master site page
mstr = CType(Master, Site)
'setup partial rendering so Log can update asynchronously
scriptManager = CType(mstr.FindControl("ScriptManager1"), ScriptManager)
scriptManager.EnablePartialRendering = True
scriptManager.AsyncPostBackTimeout = 28800
CType(mstr.FindControl("UpdatePanel1"), UpdatePanel).UpdateMode = UpdatePanelUpdateMode.Conditional
CType(mstr.FindControl("UpdatePanel1"), UpdatePanel).ChildrenAsTriggers = False
End Sub
The mark-up for the UpdatePanel looks like
<asp:UpdatePanel ID="UpdateLogPanel" UpdateMode="Conditional"
RenderMode="Inline" ChildrenAsTriggers="false" runat="server">
<ContentTemplate>
<%--The Gridview and other Hidden Fields--%>
<asp:Timer ID="myTimer" OnTick="timer_tick" runat="server" Interval="1000" Enabled="false"/>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="myTimer" EventName="Tick" />
</Triggers>
</asp:UpdatePanel>
Triggering the logging mechanism is done simply by setting
myTimer.Enabled = True
The timer_tick event looks like
Public Sub timer_tick(ByVal sender As Object, ByVal e As EventArgs)
generate_log()
//other logging logic (increment counters, "timeout" mechanism)
UpdateLogPanel.Update()
End Sub
Related
I can't seem to find anything related to the problem I'm encountering.
GOAL
I'm trying to get the value of a custom data attribute from the button (Web Control) that I created in my code-behind when the button is clicked. I'm referencing MSDN-Attribute.GetCustomAttribute Method but I'm not sure if this is appropriate for what I'm trying to do.
My current click sub is below but does not function.
Default.ascx
<asp:UpdatePanel ID="itemWrapper" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<asp:PlaceHolder ID="itemsPlaceholder" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
Default.ascx.vb (Code-behind)
Protected Sub Items_Load()
Dim dynamicID = "13"
Dim itemBtn = New Button()
itemBtn.Attributes.Add("data-custom-id", dynamicID) '<- Custom attribute I want to get
itemBtn.UseSubmitBehavior = False
itemBtn.Text = "select"
itemBtn.ID = "Button_" & dynamicID
AddHandler itemBtn.Click, AddressOf ItemSelectBtn_Click
itemsPlaceholder.Controls.Add(itemBtn)
End Sub
Public Sub ItemSelectBtn_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim clickedBtn = DirectCast(sender, Button),
clickedBtnID As String = clickedBtn.Attributes("data-custom-id")
Console.WriteLine("this button's ID is " & clickedBtnID)
End Sub
HTML OUTPUT
<div id="Content_C001_itemWrapper">
<input type="button"
name="ctl00$Content$C001$1"
value="select"
onclick="target = '_blank';__doPostBack('ctl00$Content$C001$1','')"
data-custom-id="13">
</div>
UPDATE
This was the simplest answer. Not sure why this wasn't working but as #zgood suggested to get the attribute. VB.NET uses Attributes("attribute-name") and not Attributes["attribute-name"] like in C#. Just an FYI.
Thanks for the help!
Using ASP.NET and VB.NET code behind, I have the following code:
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim I As Integer = 0
For I = 0 To 10
ListBox1.Items.Add(I)
ListBox1.DataBind()
System.Threading.Thread.Sleep(300)
Next
End Sub
The intended output of the code is to update the listbox1 control at each iteration, but what really happens is it updates the listbox1 control after the entire loop finishes..
Is there a way to update the listbox1 control as its intended by the code logic?
You need to place the ListBox inside UpdatePanel and trigger the Button1_Click event as as Async; Something like this:
<asp:UpdatePanel runat="server" ID="pnlUpdate">
<ContentTemplate>
<asp:ListBox ID="ListBox1" runat="server"></asp:ListBox>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
This should work accordingly:
Dim I As Integer = 0
For I = 0 To 10
ListBox1.Items.Add(I)
Next
If you debug then you'll see with each iteration an item is added to Items of ListBox1 control but the effect only be visible if the page/form is loaded.
I have a form with a dropdownlist, two buttons, and two Listboxes inside an UpdatePanel. The Dropdownlist, and listboxes are all bound to SqlDatasources. The dropdownlist allows you to choose your department.
The first listbox shows a list of Jobs associated with what you've selected from the department.
The second listbox shows an inverse list of those items. (Jobs in the database that are not associated with your department)
When an item is removed from the 1st listbox, it should show up in the 2nd listbox. When an item is removed from the 2nd listbox, it should show up in the 1st listbox.
This functionality allows you to add and remove jobs from your department
The are two buttons on the page function as Add and Remove buttons. Everything is working except the Listboxes will not reliably update. The Data itself is updated in the database, and if I refresh (F5) it will show correctly.
<asp:ScriptManager ID="smgrDeptsJobs" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="uPanelDeptsJobs" runat="server">
<ContentTemplate>
<asp:DropDownList ID="ddlDepartments" runat="server"
DataSourceID="sqldsDepartments" DataTextField="Department"
DataValueField="DeptID" Width="150px" AutoPostBack="True">
</asp:DropDownList>
<asp:ListBox ID="lstJobsIn" runat="server" DataSourceID="sqldsJobsIn"
DataTextField="JobName" DataValueField="JobID" height="156px"
width="220px">
</asp:ListBox>
<asp:Button ID="btnAddJob" runat="server" Text="<<" Width="70px"
CausesValidation="False" />
<asp:Button ID="btnRemoveJob" runat="server" Text=">>" Width="70px"
CausesValidation="False" />
<asp:ListBox ID="lstJobsOut" runat="server" DataSourceID="sqldsJobsOut"
DataTextField="JobName" DataValueField="JobID" height="156px"
width="220px">
</asp:ListBox>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ddlDepartments"
EventName="SelectedIndexChanged" />
<asp:AsyncPostBackTrigger ControlID="btnAddJob" EventName="Click" />
<asp:AsyncPostBackTrigger ControlID="btnRemoveJob" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
The code for the two button click events is below:
Protected Sub btnAddJob_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAddJob.Click
Dim sqlJobsDB As New SqlConnection(ConfigurationManager.ConnectionStrings("JobsDB").ConnectionString)
Dim sqlCmdInsert As SqlCommand = sqlJobsDB.CreateCommand()
sqlJobsDB.Open()
sqlCmdInsert.CommandText = _
"INSERT INTO tblDeptsJobs (DeptID, JobID) VALUES " + _
"(#DeptID, #JobID)"
' Declare the data types for the parameters
sqlCmdInsert.Parameters.Add("#DeptID", SqlDbType.TinyInt)
sqlCmdInsert.Parameters.Add("#JobID", SqlDbType.TinyInt)
' Assign the parameters values from the form
sqlCmdInsert.Parameters("#DeptID").Value = ddlDepartments.SelectedValue
sqlCmdInsert.Parameters("#JobID").Value = lstJobsOut.SelectedValue
' Execute the insert Statement
sqlCmdInsert.ExecuteNonQuery()
sqlJobsDB.Close()
End Sub
Protected Sub btnRemoveJob_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnRemoveJob.Click
Dim sqlJobsDB As New SqlConnection(ConfigurationManager.ConnectionStrings("JobsDB").ConnectionString)
Dim sqlCmdDelete As SqlCommand = sqlJobsDB.CreateCommand()
sqlJobsDB.Open()
sqlCmdDelete.CommandText = _
"DELETE FROM tblDeptsJobs WHERE tblDeptsJobs.DeptID = #DeptID AND tblDeptsJobs.JobID = #JobID"
' Declare the data types for the parameters
sqlCmdDelete.Parameters.Add("#DeptID", SqlDbType.TinyInt)
sqlCmdDelete.Parameters.Add("#JobID", SqlDbType.TinyInt)
' Assign the parameters values from the form
sqlCmdDelete.Parameters("#DeptID").Value = ddlDepartments.SelectedValue
sqlCmdDelete.Parameters("#JobID").Value = lstJobsIn.SelectedValue
' Execute the insert Statement
sqlCmdDelete.ExecuteNonQuery()
sqlJobsDB.Close()
End Sub
It feels like when I add or remove a job, the listbox that I last selected an item in, is the one that doesn't update.
I also can't get the dropdownlist to update the listboxes without setting autopostback on the dropdownlist to True.
The ugly Band-Aid fix I've come up with is using the listbox.items.clear() method and then rebinding the data for each listbox.
Basically what is happening is that you update your database but never rebind your controls. I'm not sure exactly what you will have to put into your click handlers to make this work (because I have never used the SQL datasource controls before), but it should look something like this:
Protected Sub btnAddJob_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAddJob.Click
Dim sqlJobsDB As New SqlConnection(ConfigurationManager.ConnectionStrings("JobsDB").ConnectionString)
Dim sqlCmdInsert As SqlCommand = sqlJobsDB.CreateCommand()
sqlJobsDB.Open()
sqlCmdInsert.CommandText = _
"INSERT INTO tblDeptsJobs (DeptID, JobID) VALUES " + _
"(#DeptID, #JobID)"
' Declare the data types for the parameters
sqlCmdInsert.Parameters.Add("#DeptID", SqlDbType.TinyInt)
sqlCmdInsert.Parameters.Add("#JobID", SqlDbType.TinyInt)
' Assign the parameters values from the form
sqlCmdInsert.Parameters("#DeptID").Value = ddlDepartments.SelectedValue
sqlCmdInsert.Parameters("#JobID").Value = lstJobsOut.SelectedValue
' Execute the insert Statement
sqlCmdInsert.ExecuteNonQuery()
sqlJobsDB.Close()
//may need to do explicit call to DB to get data here
//after you have the data, rebind
lstJobsIn.DataBind();
lstJobsOut.DataBind();
End Sub
That's roughly what it will look like. I would be interested to see what exactly you do to solve your problem.
Just set dropdownlist autopostback to true, remove all triggers and set ChildrenAsTriggers="true" on the updatepanel.
I am experiencing a problem with buttons and AddHandler. It only works if I use AddHandler Button1.click, AddressOf... in Page_load, but if I create the button dynamically in one of the sub routines, the event doesn't fire.
For example,
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
<asp:ListItem>1</asp:ListItem>
<asp:ListItem>2</asp:ListItem>
</asp:DropDownList>
<asp:ScriptManager id="ScriptManager1" runat="server"></asp:ScriptManager>
<asp:UpdatePanel id="UpdatePanel1" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
<contenttemplate>
<asp:PlaceHolder id="PlaceHolder1" runat="server"></asp:PlaceHolder>
</contenttemplate>
</asp:UpdatePanel>
<asp:UpdatePanel id="UpdatePanel2" runat="server" UpdateMode="Conditional">
<contenttemplate>
<asp:Label id="Label2" runat="server" Text="Label"></asp:Label>
</contenttemplate>
</asp:UpdatePanel>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
Label1.Text = Date.Now
ScriptManager1.RegisterAsyncPostBackControl(DropDownList1)
End Sub
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Label2.Text = "Panel refreshed at " + Date.Now.ToString()
End Sub
Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList1.SelectedIndexChanged
Dim b As New Button
b.Text = "Click"
ScriptManager1.RegisterAsyncPostBackControl(b)
AddHandler b.Click, AddressOf Button1_Click
PlaceHolder1.Controls.Add(b)
UpdatePanel1.Update()
End Sub
The dropdownlist works, but the button doesn't. What am I doing wrong?
You have to regenerate your dynamically created controls on every postback (at last in Page_Load, better in Page_Init). You have to set the ID of the controls accordingly because ASP.Net needs it to identify which control caused a Postback and to handle the appropriate events.
You could save the number of created buttons in ViewState and use this to regenerate them on Page_Load. Increase the number when you add a new button. Use this number also to make the Button's ID unique(append it to the ID) to ensure that its the same on every postback.
For further informations, have a look the Page-Lifecycle and ViewState with dynamically added controls.
Edit: As Joel commented, if you only need one Button you can set it's ID statically, but you have to regenerate it on postback f.e. to handle its click-event.
Just to aid anyone who has this problem and isn't quite sure how to implement. Here's a quick example.
This example starts out by displaying a dropdownlist. When user selects something from the dropdown, another dropdownlist appears.
I typed this off the top of my head, so it MAY contain errors, but you get the idea =)
In the aspx file, add a placeholder:
And in your codebehind:
...
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
'Store control count in viewstate
If Not IsPostBack Then ViewState("ControlCounter") = 1
'Rebuild dynamic controls on every postback so when page's life cycle hits Page_Load,
'selected values in the viewstate (asp.net default behavior) can be loaded into the dropdowns
Build_Dynamic_Controls()
End Sub
Protected Sub Build_Dynamic_Controls()
'Clear placeholder
myPlaceholder.Controls.Clear()
'This is where the control counter stored in the viewstate comes into play
For i as Integer = 0 To CInt(ViewState("ControlCounter") -1
Dim ddlDynamic as New DropDownList With {
.ID = "ddlDynamicDropdown" & i,
.AutoPostBack = True
}
'This is the event that will be executed when the user changes a value on the form
'and the postback occurs
AddHandler ddlDynamic.SelectedIndexChanged, AddressOf ddlDynamic_SelectedIndexChanged
'Add control to the placeholder
myPlaceholder.Controls.Add(ddl)
'Put some values into the dropdown
ddlDynamic.Items.Add("Value1")
ddlDynamic.Items.Add("Value2")
ddlDynamic.Items.Add("Value3")
Next i
End Sub
Protected Sub ddlDynamic_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)
'When a dropdown value is changed, a postback is triggered (autopostback=true)
'The event is captured here and we add another dropdown.
'First we up the control count:
ViewState("ControlCounter") = CInt(ViewState("ControlCounter")) + 1
'Now that the "total controls counter" is upped by one,
'Let's recreate the controls in the placeholder to include the new dropdown
Build_Dynamic_Controls()
End Sub
...
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>
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>
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!
Dynamic controls are pretty tricky to create. I would start by reading this.
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.