What is the cleanest way of removing a control from a container?
I'm having a WinForms performance issue that might be related to the fact that I dynamically add and then remove hundreds of controls.
EDIT { The application displays a timeline which consists of controls representing historical events. Controls are added, removed or moved, depending on the time you jump to. The performance issues are not only during the addition and removal of controls (this I can live with), but even after I jump to a time with no historical events (meaning no controls are currently displayed). After jumping around and getting to a time where there are no events on the timeline, some activities in the GUI still take a long am开发者_StackOverflow社区ount of time to complete, such as opening menus or opening dialog boxes. The strange thing is that other GUI activities, such as pressing buttons, do not stall. }
Although the memory consumption is perfectly stable, can it still be that there is an issue with freeing resources?
In order to remove a control, I do two things:
- Unregister callbacks from all events,
- Call
containerPanel.Controls.Remove(control)
.
Thanks!
As you already observed, it isn't a memory problem. My guess is, that the problem is the simple fact, that your program needs to refresh the screen that often. If you remove and add those "hundreds of controls" in one batch, you can try to disable screen refresh until you are done.
You can do this using SuspendLayout
and ResumeLayout
:
SuspendLayout();
for(...)
AddControl(...);
ResumeLayout();
and
SuspendLayout();
for(...)
RemoveControl(...);
ResumeLayout();
You might have trouble due to GC pressure, that is that the garbage collector is running often due to many objects beeing created and then freed. when the GC runs all threads are stopped in their tracks (almost) and the app looks like its freezing
i dont think you're doing anything wrong with your removal code, but perhaps you can cache the controls somehow? can you tell us a bit more about you scenario?
-edit-
Based on your scenario, i'd suggest sidestepping the whole issue with removing controls and adding new ones and if possible reusing the controls that are already in the view, but switching out their data contexts (binding them to diffrent data) when the view changes. In wpf a common name for this approach is UI-virtualization but it can be applied to any ui framework, at least in principle
Another way around the problem might be to have empty place holder controls for the for all the positions in the timeline that you can scroll to immediately and then add content to as its loaded from disk or whereever. That way you would not have to affect the layout of the whole time line, you'd just fill in the particular slot the user is viewing. This would be even more effective if all the time-line-event-controls are all the same size, then the layout of the entire timeline would be completley unaffected)
Removing lots of controls one at a time is really not something that WinForms is designed to do well.
Each call to ControlCollection.Remove
results in a call to ArrayList.RemoveAt
. If you are removing the last item in the collection this not too bad. If you are removing an item from the middle of the collection Array.Copy
will get called to shuffle all of the items after that element in the ArrayList
's internal array down to fill the empty spot.
There are a couple of approaches you could try:
Remove all the controls then add back the ones you want to keep
ArrayList l = new ArrayList();
foreach (Control c in Controls){
if (ShouldKeepControl(c))
l.Add(c);
else
UnhookEvents(c);
}
SuspendLayout();
Controls.Clear();
Controls.AddRange((Control[])l.ToArray(typeof(Control)));
ResumeLayout();
Remove last to first
/* Example assumes your controls are in the best possible
order for this technique. If they were mostly at the end
with a few in the middle a modified version of this
could still work. */
int i = Controls.Count - 1;
bool stillRemoving = true;
SuspendLayout();
while (stillRemoving && i >= 0){
Control c = Controls[i];
if (ShouldRemoveControl(c)){
UnhookEvents(c);
Controls.RemoveAt(i);
i--;
}else{
stillRemoving = false;
}
}
ResumeLayout();
The effectiveness of either approach will depend on how many controls you are keeping after removing a batch of controls and the order of the controls in the collection.
Since Control
implements IDisposable
you should also Dispose the control after removing it from its container.
containerPanel.Controls.Remove(control);
control.Dispose();
When doing hundreds of small updates to the UI of a WinForm app there might be performance issues when the UI thread over and over again redraws the interface. This especially occurs if the updates are pushed from a background thread.
If this is the problem it can render the UI totally unusable for a while. The solution is to make the updates in a way that the UI doesn't redraw until all of the pending updates are done.
Okay, this look funny but for me, the only solution which works fine for me was
For i = 0 To 3 ' just to repeat it !!
For Each con In splitContainer.Panel2.Controls
splitContainer.Panel2.Controls.Remove(con)
con.Dispose()
'con.Visible = False
Next
Next
- using suspendLayout() and resumeLayout() methods !!!
精彩评论