While wondering on ASP.NET Forums, I often see people having difficulties dealing with dynamical controls. Sometimes, it is with when they should be instantiated, how do they keep their state over postbacks, understanding page lifecycle, and often it seems that people are willing to go to the extreme solutions, using lots of precious resources just to make it work.
Luckily there are good resources on the subject, let me list a few:
Scott Mitchell's blog contains tons of useful information
http://www.scottonwriting.net/sowBlog/
He has also written many good articles, here's a few of them:
Now, what I'd also like to say is that when analyzing the scenario user has (on Forums) which requires usage of dynamical controls, I often want to question, if the work should be done that way. I do it especially when user has done it all from scratch. Sure, sometimes it has to be done so for various reasons but very often it's just about how to create the instances and then keep reinstantiating them on postback.
Sometimes instantiating dynamic controls is quite easy to do by utilizing built-in databound controls in the Framework. Take Repeater, which is very lighweight (compared to Grid controls), "repeats" based on how its databound and if you think about it, it keeps its state over postback, right And it is basically dynamic control itself, dynamic features being implemented via data-binding.
So if you had a scenario where you'd need to take input from user into TextBoxes, but user should be able to tell how many there are those TextBoxes or add them as he/she likes to. If you'd do it with a repeater, it could look as follows (Repeater's ItemTemplate has also a CheckBox and DDL to demonstrate the idea I presetn later on this post)
<asp:Repeater ID="rptTBs" runat="server" >
<ItemTemplate>
<asp:TextBox ID="tb" runat="server" />
<asp:CheckBox ID="chk" runat="server" />
<asp:DropDownList ID="drp" runat="server" >
<asp:ListItem>One</asp:ListItem>
<asp:ListItem>Two</asp:ListItem>
<asp:ListItem>Three</asp:ListItem>
</asp:DropDownList>
<br />
</ItemTemplate>
</asp:Repeater>
<asp:Button ID="btnAdd" runat="server" Text="Add a TextBox" OnClick="btnAdd_Click" />
[C#]
protected void btnAdd_Click(object sender, EventArgs e)
{
int cnt = rptTBs.Items.Count + 1;
object[] arrDummyDatasource=new object[cnt];
rptTBs.DataSource = arrDummyDatasource;
rptTBs.DataBind();
}
Now, if you try this. You note that you can add TextBoxes on the Page by clicking the "Add a TextBox" button.And if you cause a postback with any means, you note that state is kept. Only drawback is now that if you already type something into the TextBox, make a selection or something, and then again click Add button, all controls are actually cleared when new row of controls is added. This is due to that databinding the Repeater happens in a postback event, when all postback data has already been loaded into existing controls, so these data-bound created controls are basically added later than that, and Framework's automatic postback data loading won't apply.
Ok, so you need to load the data back to the controls manually by reading from Request.Form collection. That's not hard thing to do, but can be nasty to do on every page and when you have lots of controls. If you think about it, these controls loading postback data are mostly ones implementing IPostBackDataHandler interface, and in fact that's the interface to be used when controls loads their postback data. So here we go, let's create a helper function.
private void CheckPostData(ControlCollection _controls)
{
if (IsPostBack)
{
foreach (Control ctr in _controls)
{
IPostBackDataHandler hnd = ctr as IPostBackDataHandler;
if (hnd != null)
{
hnd.LoadPostData(ctr.UniqueID, Request.Form);
}
if (ctr.HasControls())
CheckPostData(ctr.Controls);
}
}
}
And to initiate this for Repeater you could wire it in ItemDataBound event.
protected void rptTBs_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
CheckPostData(e.Item.Controls);
}
}
Sure, why not , you could try doing it in PreRender and so on, for the entire Repeater at once (you'd just want to do it when control has been databound, not when it's already restored from ViewState, since then it already has worked as its supposed to). Also note that using IPostBackDataHandler interface would also cause postback data related events to fire, such as TextChanged in TextBox or SelectedIndexChanged in DropDownList.
Controls using IPostBackEventHandler interface are a number of their own, I didn't really think their use here, it is justa s possible but would it make sense to raise their events, since they are controls which basically caused the postback, so their events are to fire anyway.
Note! If you use this with ASP.NET 2.0, you need to disable event validation by setting EnableEventValidation="False" on page directive (or in web.config if you want to do it for all pages), since this uses bit hacky way to restore postback data :-)