Removing Dynamically Created Controls in C#

Removing dynamically created controls in C#

Everybody is forgetting a very important detail: you have to Dispose() the control or it will leak forever:

for (int ix = this.Controls.Count - 1; ix >= 0; ix--) {
if (this.Controls[ix] is PictureBox) this.Controls[ix].Dispose();
}

I'll put some more emphasis on the forever clause, lots of clamor about it in the comments, the Control class does not behave like any other .NET class. A Control is kept alive by its Handle property. Which stores the native Windows handle. As long as the native window exists, the Control object cannot be destroyed.

This requires the object to be kept alive artificially when you use Clear() or Remove() and remove the control from its parent. Winforms uses the so-called "parking window" as the host of such controls. It is a normal native window like any other, it is just not visible. Its job is to be the parent of such orphaned controls.

The parking window permits lots of neat tricks that are normally very hard to do in Windows. You can for example turn the ShowInTaskbar property on and off at runtime. A property of a window that can normally only be specified when you create the window (WS_EX_APPWINDOW style, specified in the CreateWindowEx() call). Winforms can do it even after you created the window by moving the controls of the form to the parking window, destroying the window, creating it again and moving the controls back. Neat.

But with the not-so-neat hangup that's the topic of this answer, if you remove the control and don't call its Dispose() method then it will continue to survive on the parking window. Forever. A true leak. Nothing that the garbage collector can do about it, it sees a valid reference to the object. A pretty gross violation of the IDisposable contract, calling Dispose() is optional but it is not for the Control class.

Luckily such a bug is pretty easy to diagnose, it doesn't require any special tooling, you can see the leak in Task Manager's Processes tab. Add the "USER Objects" column.

Removing dynamic controls from panel

You are still not saying which control you want to remove, what type of controls you want to remove or how you want to identify them.

You could just loop through the controls to remove specific Controls.

If you have Linq, its easy:

private void btn_Click(object sender, EventArgs e)
{
panel1.Controls.Clear(); //to remove all controls

//to remove all comboboxes
foreach (Control item in panel1.Controls.OfType<ComboBox>().ToList())
{
panel1.Controls.Remove(item);
}

//to remove control by Name
foreach (Control item in panel1.Controls.OfType<Control>().ToList())
{
if (item.Name == "bloodyControl")
panel1.Controls.Remove(item);
}


//to remove just one control, no Linq
foreach (Control item in panel1.Controls)
{
if (item.Name == "bloodyControl")
{
panel1.Controls.Remove(item);
break; //important step
}
}
}

Edit:

Its easy to do the same since you're tagging the control already. All you need is to just retrieve the control back from tag. But you need to tag appropriately:

Do this instead:

private void button1_Click(object sender, EventArgs e)
{
int v;
v = c++;
panel1.VerticalScroll.Value = VerticalScroll.Minimum;

Button btn = new Button();
btn.Name = "btn" + v;
btn.Text = "Remove";
btn.Location = new Point(750, 5 + (30 * v));
btn.Click += new EventHandler(btn_Click);

ComboBox combo = new ComboBox();
combo.Name = "combobox" + v ;
combo.Location = new Point(30, 5 + (30 * v));
combo.Tag = btn;

ComboBox combo2 = new ComboBox();
combo2.Name = "combobox2" + v ;
combo2.Location = new Point(170, 5 + (30 * v));
combo2.Tag = btn;

TextBox txt = new TextBox();
txt.Name = "txtbx" + v;
txt.Location = new Point(300, 5 + (30 * v));
txt.Tag = btn;

TextBox txt2 = new TextBox();
txt2.Name = "txtbx2" + v;
txt2.Location = new Point(450, 5 + (30 * v));
txt2.Tag = btn;

TextBox txt3 = new TextBox();
txt3.Name = "txtbx3" + v;
txt3.Location = new Point(600, 5 + (30 * v));
txt3.Tag = btn;

panel1.Controls.Add(combo);
panel1.Controls.Add(btn);
panel1.Controls.Add(txt);
panel1.Controls.Add(combo2);
panel1.Controls.Add(txt2);
panel1.Controls.Add(txt3);
}

private void btn_Click(object sender, EventArgs e)
{
//to remove control by Name
foreach (Control item in panel1.Controls.OfType<Control>().ToList())
{
if (item.Tag == sender || item == sender)
panel1.Controls.Remove(item);
}
}

Here you are tagging controls with the button, hence on the button click you can remove all the controls whose tags are the clicked button which you get from sender argument. But the downside of this approach is that you have to enumerate all the controls of the panel which is not great.

Edit: As I came to learn the below code is for a table layout panel which the OP isn't using for now. But anyway a table panel layout is better suited for this job.

I would suggest you to do this:

private void button1_Click(object sender, EventArgs e)
{
int v;
v = c++;
panel1.VerticalScroll.Value = VerticalScroll.Minimum;

Button btn = new Button();
btn.Name = "btn" + v;
btn.Text = "Remove";
btn.Location = new Point(750, 5 + (30 * v));
btn.Click += new EventHandler(btn_Click);
btn.Tag = v;

ComboBox combo = new ComboBox();
combo.Name = "combobox" + v ;
combo.Location = new Point(30, 5 + (30 * v));
combo.Tag = v;

ComboBox combo2 = new ComboBox();
combo2.Name = "combobox2" + v ;
combo2.Location = new Point(170, 5 + (30 * v));
combo2.Tag = v;

TextBox txt = new TextBox();
txt.Name = "txtbx" + v;
txt.Location = new Point(300, 5 + (30 * v));
txt.Tag = v;

TextBox txt2 = new TextBox();
txt2.Name = "txtbx2" + v;
txt2.Location = new Point(450, 5 + (30 * v));
txt2.Tag = v;

TextBox txt3 = new TextBox();
txt3.Name = "txtbx3" + v;
txt3.Location = new Point(600, 5 + (30 * v));
txt3.Tag = v;

panel1.Controls.Add(combo);
panel1.Controls.Add(btn);
panel1.Controls.Add(txt);
panel1.Controls.Add(combo2);
panel1.Controls.Add(txt2);
panel1.Controls.Add(txt3);
}

private void btn_Click(object sender, EventArgs e)
{
int toBeDeletedRow = (int)((Control)sender).Tag;
for (int row = panel1.RowCount - 1; row >= 0; row--)
{
if (row == toBeDeletedRow)
{
panel1.RowStyles.RemoveAt(row);
panel1.RowCount--;
return;
}
}
}

Delete dynamically created controls

Don't use "Dispose" on the labels right away. First remove them. Note that you can't modify the Controls collection inside the foreach so you have to do something like this:

List<Label> itemsToRemove = new List<Label>();
foreach (Label label in Controls.OfType<Label>())
{
if (label.Tag != null && label.Tag.ToString() == "dispose")
{
itemsToRemove.Add(label);
}
}

foreach (Label label in itemsToRemove)
{
Controls.Remove(label);
label.Dispose();
}

If you want to remove all different kinds of controls in one swoop:

List<Control> itemsToRemove = new List<Control>();
foreach (Control ctrl in Controls)
{
if (ctrl.Tag != null && ctrl.Tag.ToString() == "dispose")
{
itemsToRemove.Add(ctrl);
}
}

foreach (Control ctrl in itemsToRemove)
{
Controls.Remove(ctrl);
ctrl.Dispose();
}

C# removing controls dynamically, what does it really do?

When you call IDisposable.Dispose, it does not mean that the object is deleted. Disposing an object is intended to clean up any internal or external resources. In WinForms this specifically means cleaning up any underlying window handles etc.

In .NET managed memory, only the garbage collector will ultimately remove the object. In your specific case though the GC will never collect your removed controls, because you still have them in your list. Having them in the list means that the objects are still referenced, i.e. they are not dead. The GC will only collect dead objects.

Just manually remove the control from your list and you'll be fine. You can also listen to the Disposed event of the control and remove it from the list in the event handler. That will automatically take care of removing any disposed control from your list.

A more advanced solution would be to not add the controls directly to your list, but to add weak references. A WeakReference allows you to access the object, but it will not prevent garbage collection. However, I personally would prefere to dereference the object manually, since this is more straightforward and less error prone.

Delete dynamically created controls from the middle of a set

When developing an ASP.NET Forms application, as long as you operate within the context of the ASP.NET Forms framework and objects between page load and post-backs you will be safe. Albeit, it is sometimes challenging to do without adding custom code. Handling that in the code behind is exactly the best place for this, for adding controls dynamically to a page.



When you remove the control, ViewState should be updated automatically as you are using a ASP.NET Forms method directly on a control when you call parent.Controls.Remove("ID"). In the case you are custom naming the controls, you will need to provide all the id's or names yourself so that they are distinct for the page. So here I am under the assumption you have custom named the control, and know what to really put in place for "ID".



ViewState is generally transparent to you as far as coding is concerned. Sometimes you may find yourself manually clearing ViewState between post-backs (like for a form with a password, when every visit to the page you need to clear the value). ViewState can also be turned off, so think about whether
or not your app needs the extra overhead for ViewState. On dynamic forms, I would do my best to implement adding controls or data binding to custom data sources in the code behind. The reason being is registering client-side scripts, so ASP.NET Forms framework knows about the scripts, and handling a custom id attribute or naming schemes client-side can be hard to debug or deal with from a binding standpoint.



I have personally integrated an ASP.NET Forms application with AngularJS 1.6, and while it was more difficult, and needed more configuration than with ASP.NET MVC, it was still possible and offered me a nice way to build dynamic forms out of ASP.NET's hands. However, binding back to your form values in the code behind requires looking and fetching values out of Request.Form Collection. If done in this manner, where your form is external to ASP.NET there will be no ViewState for those objects.


Does this update the ViewState so that I can just decrement my counter by 1 and renumber the remaining controls from 0 to 3 or do I need to keep track of which one was deleted and keep the numbering 0, 1, 3, 4?



As far as the strategy you are mentioning here a code sample would be most helpful. I have to make a lot of assumptions there to guess what might be happening. I could tell you it's okay, but you could still be missing something effecting the outcome in your personal results. When deleting a row, it will update the ViewState automatically. As far as keeping track of your numbering scheme for how you name your control Id's with distinct values, I would personally not leave gap's in the numbers, so stick to 0 to 3, and it should simplify your code and you will not have to track as much in the code behind or it will not effect your rendering logic. This way the controls bind uniquely back to your code behind during a post-back, and you can just iterate easily through the control collections, without worrying about naming schemes as much.

Remove dynamic control

for example dtxt2 does not get removed the first time but if I run the remove function a second time it does get removed.

... if you have time could you further explain what my fault was?

When you iterate through a collection you have an iterator in the background that holds the current position and is checked against the length of the collection. So if the collection has 3 items and the iterator is at the position 2

[X1] [X2] [X3]  // Length 3
^
|
iterator

Now you remove the second element

[X1] [X3]  // Length 2
^
|
iterator

the iterator has still its value, but now it is pointing to the next element, and unfortunately this is the last in the collection. Now the loop will increment the iterator and check whether it is already larger than the amount of items in the collection. If so it will cancel the loop, because there are no more items to iterate. X3 is thus never processed

[X1] [X3]      // Length 2
^
|
iterator

This is why your second textbox got skipped in the first place.

when removing items from a collection during iteration through the collection one would use a reversed loop. starting from the end and going down to 0:

void removeDynamics()
{
for (int i = this.Controls.Count - 1; i >= 0; i--)
{
Control x = this.Controls[i];
if (x.Name.Contains("Dynamic"))
{
this.Controls.Remove(x);
x.Dispose();
}
}
}

You could of course also filter the controls collection and get only the matching items first and then remove them:

List<TextBox> toBeRemoved = this.Controls.OfType<TextBox>()
.Where(x => x.Name.Contains("Dynamic")).ToList();
toBeRemoved.ForEach(x => { this.Controls.Remove(x); x.Dispose(); });

removing dynamic controls from the panel

foreach(var item in panel1.Controls)
{

if(item is TextBox || item is ComboBox)
{
panel1.Controls.Remove(item);
}

}

Or alternatively you could try this below.

 var list = (from object item in panel1.Controls where item is TextBox || item is ComboBox select item as Control).ToList();

list.ForEach(x => panel1.Controls.Remove(x));

C# Removing dynamic created controls in a flowLayoutPanel

You have two options:

First Option

You can add a button to your User Control to dispose itself.

this.Dispose();


Second Option

You can add a property to your form to track selected User Control.

Form1:

public Control SelectedItem { get; set; } = null;

private void DeleteButton_Click(object sender, EventArgs e)
{
if (SelectedItem != null)
{
SelectedItem.Dispose();
}
}

UserControl1:

// You can use any Event you prefer (Enter, Click or etc.).
private void GroupBox1_Enter(object sender, EventArgs e)
{
// First Parent is FlowLayoutPanel
// Second Parent is your Form
Form1 parent = this.Parent.Parent as Form1;
if (parent != null)
{
parent.SelectedItem = this;
}
}


Related Topics



Leave a reply



Submit