How to dynamically add controls and preserve Viewstate? - asp.net

I'm attempting to use Controls.AddAt(), but it apparently breaks controls at later indexes:
Here's my minimal example:
Aspx put in a form:
<asp:DropDownList runat="server" ID="ddl" />
<asp:Button Text="text" runat="server" OnClick="Unnamed2_Click" />
Code Behind:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ddl.Items.Add("Click the button")
'Controls.Add(New HyperLink) 'Works fine, but is put at end of collection.
'Controls.AddAt(2 ,New HyperLink) 'Is also safe but I wanted the control first
Controls.AddAt(0, New HyperLink) 'ddl loses it's item after postback
End If
End Sub
On the first postback of the page after calling AddAt, the DropDownList loses it's item. It doesn't matter what kind of control I add even HTMLControls. Viewstate is not disabled.
How do I dynamically add controls without breaking others?

If you used a PlaceHolder to add your HyperLink into, it would not mess up the rest of the page:
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
<asp:DropDownList ID="ddl" EnableViewState="true" runat="server" />
<asp:Button ID="bn1" Text="text" OnClick="Unnamed2_Click" runat="server" />
With code like
Protected Sub Unnamed2_Click(sender As Object, e As EventArgs) Handles bn1.Click
Dim newItem = "Click the button" & DateTime.Now.ToString("HH:mm:ss")
ddl.Items.Add(newItem)
ddl.SelectedIndex = ddl.Items.Count - 1
PlaceHolder1.Controls.Add(New HyperLink With {.ID = "hyp", .Text = "Hyperlink here"})
End Sub
And always give your asp:Controls an ID if they take one.

Related

Force Visible Property to Apply During Button Click

I'm trying to hide a button and replace it with a label until a query finishes to populate a grid view. My button .aspx page looks like so:
<asp:Panel runat="server" ID="pnlSelect" HorizontalAlign="Center" >
<asp:Label runat="server" Text="Please Select an Item" />
<br />
<br />
<asp:DropDownList runat="server" ID="ddlItems" AutoPostBack="true" />
<br />
<br />
<asp:Button runat="server" ID="btnItemSubmit" Text="Submit" /> <asp:Label runat="server" ID="lblItemSubmit" Text="Please Wait" Visible="false" />
</asp:Panel>
and here is the .aspx.vb code behind that is handling execution:
Protected Sub btnItemSubmit_Click(sender As Object, e As EventArgs) Handles btnItemSubmit.Click
btnItemSubmit.Visible = False
lblItemSubmit.Visible = True
oracleSql()
btnItemSubmit.Visible = True
lblItemSubmit.Visible = False
End Sub
My problem is that the properties don't apply until after the sub finishes execution, at which point no changes are visibly made because the properties immediately went back to how they were originally. Is there a way to get these properties to apply asynchronously?
Edit:
Here is Page_Load
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack Then
'was causing query to reload on button click and default back to first item on list, this way allows it to retain whichever item was selected
'setting postback = false on ddl did not resolve the issue either
Else
Dim oracleConn As New Oracle.DataAccess.Client.OracleConnection
oracleConn.ConnectionString = ConfigurationManager.ConnectionStrings("ConnString").ConnectionString
Dim sql As New Oracle.DataAccess.Client.OracleCommand
sql.CommandType = CommandType.Text
sql.CommandText = "select * from Items order by 1"
sql.Connection = oracleConn
Dim dataAdapter As New Oracle.DataAccess.Client.DataAdapter(sql)
Dim dataSet As New DataSet
dataAdapter.Fill(dataSet)
ddlItems.DataSource = dataSet
ddlItems.DataTextField = "item_name"
ddlItems.DataValueField = "item_id"
ddlItems.DataBind()
End If
End Sub
oracleSql() is similar to this, just binding to a gridview instead of a ddl.
Based on recommendations from JohnPete22, I ended up creating a simple Javascript function to handle this:
<script type="text/javascript">
function hideShowBtn() { document.getElementById('btnItemSubmit').style.visibility = 'hidden'; };
</script>
I then added it to the OnClientClick property of my button:
<asp:Button runat="server" ID="btnItemSubmit" Text="Submit" OnClientClick="hideShowBtn()" />
The button would disappear when clicked and return after PostBack.

PlaceHolders and Persistent Dynamic Controls (ADVANCED)

Warning! This problem is not for the feint of heart. I've spent several days in all trying to troubleshoot it.
I have a Wizard control with about 5 steps, each with several controls ranging from very simple to very complex such as custom jQuery combo boxes based on DropDownLists and TextBoxes. Each step has several of the controls wrapped in a PlaceHolder control.
The Wizard along with all PlaceHolders and their child Controls are nested inside of a View of a MultiView. On the same page I have another View styled like a Form, but not one. This view has corresponding PlaceHolders for each of PlaceHolders within each step of the Wizard.
Depending on the ReferralUrl I call the following function to "Toggle" the view from the Wizard to the form style view by moving all the controls, then setting the active view as follows:
Protected Sub ToggleView() Handles ViewToggle.Click
If Wizard_mv.ActiveViewIndex = 0 Then
ViewToggle.Text = "Toggle Wizard View"
fPH1.Controls.Add(wPH1)
fPH2.Controls.Add(wPH2)
fPH3.Controls.Add(wPH3)
fPH4.Controls.Add(wPH4)
fPH5.Controls.Add(wPH5)
Wizard_mv.ActiveViewIndex = 1
ElseIf Wizard_mv.ActiveViewIndex = 1 Then
ViewToggle.Text = "Toggle Form View"
wPH1.Controls.Add(fPH1)
wPH2.Controls.Add(fPH2)
wPH3.Controls.Add(fPH3)
wPH4.Controls.Add(fPH4)
wPH5.Controls.Add(fPH5)
Wizard_mv.ActiveViewIndex = 0
End If
End Sub
Immediately after this, I use another function for pre-filling the controls with values from a record in a database. After allowing the users to make some changes, they may resubmit the updated record to the database. The problem is that this works just fine if I do so from the Wizard but not after toggling. The trace shows that the Control Tree is empty at the time of submitting the updated record, and hence I cannot grab the user-entered/pre-filled values from this view. The pre-filling works great and the "Selected" values are all correct. The problem arises on the PostBack after clicking submit and it loses all the values and controls.
Please do not answer unless you fully understand my problem and are willing to help. I think the problem very well lies within the page lifecycle. Mysteriously, when I submit from my Wizard, on the postback in Page_Init I get my control values loaded, however when I submit from my form view, neither the controls nor their values are loaded. From what I read, this is an issue of persistence. Really hoping there's a relatively easy solution for this.
Here's a sample of my markup in my Wizard (all styling removed for brevity):
<asp:WizardStep ID="WizardStep1" runat="server" Title="Product Info">
<asp:PlaceHolder ID="wPH1" runat="server" ViewStateMode="Enabled">
<asp:Table ID="ProductInfoTable" runat="server" Width="100%">
<asp:TableHeaderRow>
<asp:TableHeaderCell><h3>Product Line</h3></asp:TableHeaderCell>
<asp:TableHeaderCell><h3>Publication Type</h3></asp:TableHeaderCell>
<asp:TableHeaderCell><h3>Request Type</h3></asp:TableHeaderCell>
</asp:TableHeaderRow>
<asp:TableRow>
<asp:TableCell>
<div class="ui-widget">
<!-- Autocomplete Combobox -->
<asp:DropDownList ID="productLine_ddl" runat="server" DataSourceID="productLineSDS" ViewStateMode="Enabled" DataTextField="Product" DataValueField="ID"></asp:DropDownList>
<asp:TextBox ID="productLine_cb" runat="server" EnableViewState="True"></asp:TextBox>
<button id="productLine_btn" type="button" title="Show All Items"></button>
</div>
</asp:TableCell>
<asp:TableCell>
<asp:DropDownList ID="docType_ddl" runat="server" DataSourceID="docTypeSDS" DataTextField="DocType" DataValueField="ID"></asp:DropDownList>
</asp:TableCell>
<asp:TableCell>
<asp:DropDownList ID="requestType_ddl" runat="server" DataSourceID="requestTypeSDS" DataTextField="RequestType" DataValueField="ID"></asp:DropDownList>
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell ColumnSpan="2">
<asp:MultiView ID="Attachment_mv" runat="server" ActiveViewIndex="0">
<!-- File Upload/Browsing Display -->
<asp:View ID="AttachmentUploadView" runat="server">
<h3 class="inlineH">Attach File: </h3>
<asp:FileUpload ID="AttachmentFile_btn" runat="server" />
<asp:Button ID="UploadFile_btn" runat="server" Text="Upload File" />
</asp:View>
<!-- File Attached Display -->
<asp:View ID="FileAttachedView" runat="server">
<h3 class="inlineH">Uploaded File: </h3>
<asp:Label ID="FileAttachedLabel" runat="server" Text="Label"></asp:Label>
<asp:Literal ID="FilesOnServer" runat="server" />
</asp:View>
</asp:MultiView>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
</asp:PlaceHolder>
</asp:WizardStep>
My Page Lifecycle Events, as requested (in chronological order for your convenience) :)
Dim referrerPage As String
'Initialize Dynamic controls here. These are controls that need to be prefilled, etc.
Private Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Init
'DO NOT PREFILL at this stage, as the Controls are not yet rendered and will cause errors.
' Use this section for detecting the "referrerPage" and missing "id" parameters when expected, etc.
If Not IsPostBack Then
ViewState.Clear()
End If
Try
referrerPage = Right(Request.UrlReferrer.AbsolutePath, Len(Request.UrlReferrer.AbsolutePath) - Request.UrlReferrer.AbsolutePath.LastIndexOf("/"c) - 1)
Catch ex As Exception
If Not String.IsNullOrEmpty(Session.Item("referrerPage")) Then
referrerPage = Session.Item("referrerPage")
End If
End Try
If StrComp(referrerPage, "wizard.aspx") <> 0 And String.IsNullOrEmpty(Session.Item("referrerPage")) Then 'Initialize Session state to remember "referrerPage"
Session.Add("referrerPage", referrerPage)
End If
If StrComp(referrerPage, "formdetails.aspx") = 0 Then
If String.IsNullOrEmpty(Request.Params("id")) Then
'This line checks for an expected "id" param value and if none exists, forwards the page back to "tcom.aspx"
Response.Redirect(Request.UrlReferrer.AbsolutePath)
Else
ToggleView()
End If
End If
End Sub
'Prefill Dynamic controls here.
Private Sub Page_PreLoad(sender As Object, e As EventArgs) Handles Me.PreLoad
If Not IsPostBack Then
productLine_ddl.DataBind()
docType_ddl.DataBind()
requestType_ddl.DataBind()
'...and several more DataBinds for each individual
' control in the wizard. Nothing more.
End If
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
If Not IsPostBack Then
'Benign code for querying the database to get User info for Page.User.Identity.Name
End If
End Sub
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PreRender
If Not IsPostBack Then
'Here is completely benign code for hiding a couple controls.
End If
End Sub
Protected Sub Page_PreRenderComplete(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PreRenderComplete
'PREFILL HERE when appropriate. This occurs after checking the "referrerPage" and ensuring a value for the "id" parameter.
If Not IsPostBack Then
Try
If Not String.IsNullOrEmpty(Request.Params("id")) Then
PrefillWizard(Request.Params("id"))
Else : output.Text += "Source: " + Request.UrlReferrer.AbsolutePath
End If
Catch ex As Exception
End Try
End If
End Sub
To anyone who can help with this, you have my eternal gratitude. ;)
The problem is that the control tree of the page must be exactly the same during every postback. That means that you have to add all the control every time no mater which ActiveViewIndex is set. Preferably in CreateChildControls or page init. In the ToggleView function you can than set the visibility of the controls.

Addhandler, button.click not firing using VB.NET

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
...

"Inline" function call: function is never hit

I'm trying to make a tabbed menu for the asp.net website I'm working on. One of the pre-requisite is obviously to color the current tab differently, so the user can know on which tab is currently is.
To do this, I made this method in my code-behind:
Protected Function GetCssClass(ByVal ctrl As LinkButton) As String
If ctrl.ID = currentLink Then
Return "current"
Else
Return String.Empty
End If
End Function
and I call the method like this in my aspx page:
<asp:LinkButton ID="LinkButton1" runat="server" PostBackUrl="/Default.aspx" CssClass="<%#GetCssClass(LinkButton1) %>" >Home</asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server" PostBackUrl="/Directory/page1.aspx" CssClass="<%#GetCssClass(LinkButton2) %>" >Page1</asp:LinkButton>
But the method is never hit... As I understand, the method should be called each time the LinkButton is drawn...
Does someone have an idea why?
Thanks in advance !
Edit: Just as a precision, all this code is in the masterpage.
Edit2: Here are the changes I made according to Quagland's suggestion.
In the aspx masterpage:
<asp:HiddenField ID="currentLink" runat="server" />
<asp:LinkButton ID="LinkButton1" runat="server" PostBackUrl="/Default.aspx" OnClick="LinkButton_Click" OnPreRender="LinkButton_PreRender" >Home</asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server" PostBackUrl="/OtherDirectory/Page1.aspx" OnClick="LinkButton_Click" OnPreRender="LinkButton_PreRender" >Page1</asp:LinkButton>
<asp:LinkButton ID="LinkButton3" runat="server" PostBackUrl="/OtherDirectory/Page2.aspx" OnClick="LinkButton_Click" OnPreRender="LinkButton_PreRender" >Page2</asp:LinkButton>
<asp:LinkButton ID="LinkButton4" runat="server" PostBackUrl="/OtherDirectory/Page3.aspx" OnClick="LinkButton_Click" OnPreRender="LinkButton_PreRender" >Page3</asp:LinkButton>
And in the code behind:
Protected Sub LinkButton_Click(ByVal sender As Object, ByVal e As EventArgs) Handles LinkButton1.Click, LinkButton2.Click, LinkButton3.Click, LinkButton4.Click, LinkButton5.Click, LinkButton6.Click, LinkButton7.Click, LinkButton8.Click
Dim lnk As LinkButton = CType(sender, LinkButton)
currentLink.Value = lnk.ID
End Sub
Protected Function GetCssClass(ByVal ctrl As LinkButton) As String
If ctrl.ID = currentLink.Value Then
Return "current"
Else
Return String.Empty
End If
End Function
Protected Sub LinkButton_PreRender(ByVal sender As Object, ByVal e As EventArgs) Handles LinkButton1.PreRender, LinkButton2.PreRender, LinkButton3.PreRender, LinkButton4.PreRender, LinkButton5.PreRender, LinkButton6.PreRender, LinkButton7.PreRender, LinkButton8.PreRender
Dim lnk As LinkButton = CType(sender, LinkButton)
lnk.CssClass = GetCssClass(lnk)
End Sub
The problem is now that the click event is not always fired. On first click, nothing happens, but on second click on a tab, the click event is correctly triggered. Any clue ?
Edit3: Could it be that the value stored in the hidden field is reset each time the masterpage is reloaded (I mean, each link points to a couple masterpage + content page) ?
You need to use <%= ... %> (i.e. replace # with =).
The hash form is used with data binding, you want to just create output.
For .NET 4 prefer <%: ... %> (use a colon) to automatically do HTML encoding.
I have quickly tested a solution that might work for you. I'm just not sure where your 'currentlink' variable comes from. I have implemented it as a hidden field here.
In Masterpage.aspx:
<asp:HiddenField ID="currentLink" runat="server" />
<asp:LinkButton ID="LinkButton1" runat="server">Home</asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server">Page1</asp:LinkButton>
In code behind:
use your original GetCssClass function add this:
Protected Sub LinkButton_PreRender(ByVal sender As Object, ByVal e As System.EventArgs)
Handles LinkButton1.PreRender, LinkButton2.PreRender
Dim lnk As LinkButton = CType(sender, LinkButton)
lnk.CssClass = GetCssClass(lnk)
End Sub
I have put it in PreRender because i was using the LinkButton click events to set the hidden field value (and Click happens after Load but before PreRender):
Protected Sub LinkButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles LinkButton1.Click, LinkButton2.Click
Dim lnk As LinkButton = CType(sender, LinkButton)
currentLink.Value = lnk.ID
End Sub
also, if there are numerous Linkbuttons, you may want to declare like:
<asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton_Click" OnPreRender="LinkButton_PreRender">Home</asp:LinkButton>
EDIT:
Here is another solution that will work with cross page postbacks. Not much code but you'll need to put some on every page. Anyway:
Masterpage.aspx:
<asp:LinkButton ID="LinkButton1" runat="server" PostBackUrl="Default.aspx">Home</asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server" PostBackUrl="Page1.aspx">Page1</asp:LinkButton>
<asp:LinkButton ID="LinkButton3" runat="server" PostBackUrl="Page2.aspx">Page2</asp:LinkButton>
<asp:LinkButton ID="LinkButton4" runat="server" PostBackUrl="Page3.aspx">Page3</asp:LinkButton>
Masterpage.aspx.vb:
Public Sub SetCssClass(ByVal ctrl As String)
CType(FindControl(ctrl), LinkButton).CssClass = "current"
End Sub
All content pages:
*.aspx:
add this directive to create a strongly typed reference to the masterpage
<%# MasterType VirtualPath="~/MasterPage.master" %>
*.aspx.vb:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Master.SetCssClass("LinkButton1") '' << put the name of the relevant link button here
End Sub
Set the CssClass in the code-behind. This will state your intent more clearly than calling DataBind on your linkbuttons if you use databinding syntax. Since you're doing it for multiple controls maybe stick them in a method called ApplyCssClasses.
I'm not sure why <%= %> isn't working but personally I don't like to mix code and markup, I like to keep them completely separate.
I would suggest setting the CSS class in code perhaps on the page load event.
lnkPopulate.CssClass = "current"
<%# ... %> will work if you're OK with doing LinkButton1.DataBind() in your code-behind at the appropriate time.

Dynamically add drop down lists and remember them through postbacks

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.

Resources