It's easy to add one control in asp.net during runtime, but it's not so straight-forward to add a bunch dynamically
You would think to add more and more controls, you could simply do something like this on a button click event (I will be using LiteralControls for ease of explanation, but this works the same for all controls, even custom ones):
somePanel.Controls.Add(new LiteralControl("<div>I AM A NEW CONTROL</div>"));
Which does work perfectly, and does exactly what you think it would do. But, if you click it again, guess what? It just re-makes the same control, replacing the old one. Doing this over and over. This is because what is produced is not kept in any sort of persistent state. You need to 'tell' your program what you want it to keep. For this, I like to use Session variables... and if you don't like that, then too bad!
With that said, we will need some sort of way to keep all the controls in an easily accessable structure. My choice here is a Generic List as they are so easy to work with and offer so much that you can do do/with them. To start with, I will be working with a basic List<LiteralControl> but I will get on later to a list with multiple controls contained within it as it is likely that you will want to add multiple controls at each click.
Now we just need to show how this is going to be persistant. First, we need to declare a list in the class. Then, each time a control is added, it must be pushed into the list as well as the page as to keep a 'copy' of it. But that still will not survice a postback. We also need to put the List into a Session variable. And now that we have that list of controls in the session, we need to check every Page_Load to see if there is any controls we have stowed away, and if so, push them to the page. Here is the most basic way to show this with functioning code:
aspx
<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Panel ID="pnl" runat="server" />
code-behind (c#)
List<LiteralControl> persistControls = new List<LiteralControl>();
protected void Page_Load(object sender, EventArgs e)
{
// if you already have some controls populated
if (Session["persistControls"] != null)
{
// pull them out of the session
persistControls = (List<LiteralControl>)Session["persistControls"];
foreach (LiteralControl lc in persistControls)
pnl.Controls.Add(lc); // and push them back into the page
}
}
protected void btn_Click(object sender, EventArgs e)
{
// basic control for demo
LiteralControl lc = new LiteralControl("<div
style=\"border:solid 2px navy;padding:3px;margin:3px\">NEW
CONTROL</div>");
pnl.Controls.Add(lc);// add it to your page
persistControls.Add(lc);// add it to the list
Session["persistControls"] = persistControls; // put it in the session
}
Now that is just a basic example to get the point across that this is actually a pretty easy concept. Now on to a bit more functionality. There are two basic things I would think an average use of this would require:
- Ability to delete each 'set' of controls
- Ability to add a bunch of controls each time and persist all of them
Turns out both of these are also quite easy given the structure we are using.
Looking at the first one: Ability to delete each 'set' of controls should be no problem as we are using a generic List. What does a List have that makes this so simple? List.Remove(List Item) In fact, using a List makes this ridiculously easy. Simply add a btnDelete_Click method to our code-behind, Now every time a control is added, a Button must also be added with a corresponding CommandArgument that relates to it's position in the List and add a Button.Click += new EventHandler(btnDelete_Click) so the new delete method will be called. Since the List itself will automatically adjust it's size and move it's items dynamically we can simply use an integer to count up from 0 each time we push a control to the page. And when a new Control is added, all that is needed is to add the List.Count to the CommandArgument.
Now on to: Ability to add a bunch of controls each time and persist all of them which will be even easier. Instead of passing just one Control like above, we can pass a collection of Controls inside another control. For this I chose Panels for the easy to work with <div> that they produce. So now, every time you add a Control, you simply add all your Controls to a Panel, then push that Panel to the List (which is now a List<Panel> by the way). And really that is all you need to do.
With those two additions, the example code now looks like this:
aspx
<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Button ID="btnClear" runat="server" Text="Reset" onclick="btnClear_Click" />
<br /><br />
<asp:PlaceHolder ID="ph" runat="server" />
code-behind (c#)
List<Panel> persistControls = new List<Panel>();
Random rand = new Random(); // for display so we can get a simple difference in controls
protected void Page_Load(object sender, EventArgs e)
{
// if you already have some controls populated
if (Session["persistControls"] != null)
{
persistControls = (List<Panel>)Session["persistControls"]; // pull them out of the session
int count = 0;
foreach (Panel lc in persistControls)
{
lc.CssClass = "smallPanel";
Button btn = new Button();
btn.Click += new EventHandler(btnDelete_Click);
btn.Text = "Delete";
btn.CommandArgument = count.ToString();
ph.Controls.Add(lc); // and push them back into the page
ph.Controls.Add(btn);
ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
count++;
}
}
}
protected void btn_Click(object sender, EventArgs e)
{
LiteralControl lc1 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ "+ rand.Next(1000,9999).ToString() + "] </span>");
LiteralControl lc2 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ " + rand.Next(1000, 9999).ToString() + "] </span>");
Panel pnl = new Panel();
pnl.Controls.Add(lc1);
pnl.Controls.Add(lc2);
Button btn = new Button();
btn.Click += new EventHandler(btnDelete_Click);
btn.Text = "Delete";
btn.CommandArgument = persistControls.Count.ToString();
ph.Controls.Add(pnl); // and push them back into the page
persistControls.Add(pnl);// add it to the list
ph.Controls.Add(btn);
ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
Session["persistControls"] = persistControls; // put it in the session
}
protected void btnClear_Click(object sender, EventArgs e)
{
Session["persistControls"] = null;
Response.Redirect(Request.Url.ToString());
}
protected void btnDelete_Click(object sender, EventArgs e)
{
int deleteThisOne = int.Parse(((Button)sender).CommandArgument);
persistControls.Remove(persistControls[deleteThisOne]);
Session["persistControls"] = persistControls; // put it in the session
Response.Redirect(Request.Url.ToString());
}
And that is all you need to do. This could be cleaned up a bit and redundant code could be consolidated, but you get the idea. This will work with any group of controls be it TextBoxes, DropDownLists or even custom Controls that you make. I throw mine inside an UpdatePanel to make it function smoother. Remember though, since this is using Session, high traffic or large control collection can make this a bit of a memory hog, so be careful.
Here is an example and the code:
code: