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 amount 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 !!!
Related
I'm going to use pseudocode in this question, so please, refer to this in theoretical aspect (I mean, there will be some simplifications)
Assume this situation:
I have MainWindow of my application, which has TabControl. In order to remove item from its collection, I'm using static function of MainWindow as follows:
public static void CloseTab(string someKindOfTabIdentity)
{
var tab = myTabControl.Items.FirstOfDefault(someScenario);
if (tab != null)
{
myTabControl.Items.Remove(tab);
tab.Content = null;
tab = null;
GC.Collect();
}
}
Now, I have Page1, where I allow user to close tab, via some function, let's say
private void GoToPage2()
{
MainWindow.CreatePage2AddToTabControlAndNavigateToIt()
MainWindow.CloseTab(myCurrentPage1Tab);
App.Cursor = Cursors.Arrow;
}
This function should create new Tab, assign content to it and then, using MainWindow.CloseTab(myCurrentPage1Tab);, close current Tab, which contains Page1.
Here are questions:
What happens to memory alocation of Page1 after MainWindow.CloseTab(myCurrentPage1Tab); line?
What happens to memory alocation of Page1 if there is a code after MainWindow.CloseTab(myCurrentPage1Tab); line?
When Page1 is going to be fully released from memory?
Is there any better (more efficient) way to achieve this?
This simplified scenario is what is happening to my WPF application all the time and I'm concerned if this is a safe way to manage tab items and memory of my app.
The instance of Page1 will become eligible for garbage collection provided that it's not referenced by any other object that is still alive.
Nothing additional, unless the "code after MainWindow.CloseTab(myCurrentPage1Tab); line" does something with the Page1 reference that prevents the instance from being collected.
When the garbage collector has collected it. When this happens is nondeterministic, i.e. you don't really know when it happens and you shouldn't really care.
Well, there is no reason to explicitly call GC.Collect. This is almost always a bad idea. Provided that the instance of the Page1 is no longer referenced in your application, it will be collected eventually anyway. Also, I don't know why you are using static methods but I guess that's another story.
To sum up, you should simply make sure that there are no references in your code that keeps the page alive longer than than necessary but stay out of way of the garbage collector.
1-3
The garbage collector will remove unreferenced controls from memory when it gets round to doing so. Exactly when that happens will depend on what else your app is doing.
Exactly when some control is cleaned up by the collector doesn't usually matter. If it does to you then maybe you have a problem in your design.
4
Depends what you're doing. Almost all dev teams use mvvm with wpf. The usual approach would be to bind a collection of viewmodels to the itemssource of that tabcontrol and template those out into tabs. Removing a tab would involve removing a viewmodel out that list.
Only the current tab would be templated out into UI with this approach.
Your description makes this sound rather like navigation though. A common pattern used for mvvm style navigation is viewmodel first ( you should be able to google a bunch of examples ). Essentially this would involve exposing a property from a window viewmodel that would hold a viewmodel for current view. That would be bound to the content of a contentcontrol and templated into UI based on datatype.
Currently I have this code all throughout my program in many places such as form resizing events, splitter moving events, document size changes, et cetera:
hsc.Value = (int)MathHelper.Clamp(hsc.Value, 0, hsc.Maximum - hsc.LargeChange + 1);
vsc.Value = (int)MathHelper.Clamp(vsc.Value, 0, vsc.Maximum - vsc.LargeChange + 1);
I'm wondering whether or not it would be better to just put it in a main loop because my program has a drawing code that is called whenever the application goes idle (very often).
The disadvantage of having this code in events is because it is code repetition and I might miss an event. The disadvantage of having it in the loop is that it may not be needed each loop and it is wasted processor cycles. However, it may be premature optimization because it probably would not be noticeable to the end-user.
What framework are you using? (WinForms, WPF, Silverlight, etc)
If it's in every Window / Form / View / UserControl then you might consider putting this functionality in a base class and pointing all these events to event handlers in the base class.
Neither of your solutions seem correct - you shouldn't be redrawing in a 'main loop' if nothing has changed, and you shouldn't be copy/pasting duplicated code throughout your app.
In a WinForms application I have a number of instances where I add a control to a container in response to a user action (panel.Controls.Add(new CustomControl(...))), then later clear the panel (panel.Controls.Clear()) and reuse it.
In production, the app occasionally throws an exception relating to GDI errors or failing to load an ImageList. This usually happens on machines with limited resources and with users that use the application intensively over the day. It seems pretty obvious that I have a GDI handle leak and that I should be disposing the controls that get cleared from the container, however any explanations I can find are vague about where and when the control should be disposed.
Should I dispose the child controls immediately after clearing the container? Something like:
var controls = new List<Control>(_panel.Controls.Cast<Control>());
_panel.Controls.Clear();
foreach (var c in controls) c.Dispose();
Or should I track the controls in a list and call dispose in the container's Dispose() method? Such as:
List<Control> _controlsToDispose = new List<Control>();
void ClearControls()
{
_controlsToDispose.AddRange(_panel.Controls.Cast<Control>());
_panel.Controls.Clear();
}
void Dispose()
{
...
foreach (var c in _controlsToDispose) c.Dispose();
}
Option 2 introduces another list which you would need to cleanup and it will take some more memory for those items. I would prefer option 1 with a try catch wrapped around the code you mentioned.
After (somewhat effectively) correcting any cases where my app wasn't disposing cleared controls I can come up with some points:
Sometimes I've pre-built a list of controls, stored for example in the Tag property of a collection of ListViewItems or TreeViewItems. They shouldn't be disposed on clear, but the entire list should be iterated and ((Control)item.Tag).Dispose() called in the parent's Dispose() method.
If the control isn't going to be used again, which can happen when I create it on the fly, it should be disposed when it is cleared from the container.
When clearing and adding controls on the fly you need to consider the lifecycle of the controls to determine whether to dispose them immediately, defer it until the parent is being disposed, or to not worry about it.
I had a situation where I removed a control to display a 'Loading...' message, then dropped the control back in later, in response to a thread completing. I added a call to dispose the control when I removed it, which caused errors when trying to add it again. Because of the threading issue it wasn't straightforward to debug. The point is that the lifecycle can depend on threads other than the UI thread. The case in point was a matter of 20 seconds after the form was displayed, so at least the control still existed. Managing a situation where a control can be destroyed with threads still wanting to refer to it is probably a case for weak events.
I haven't been able to find any best practices or recommendations on managing control lifecycle and disposal. I guess the rule is just that if a control doesn't end it's life nested on a control that is disposed, it has to be disposed manually, whenever it isn't going to be used again, or in the parent control's Dispose() method at the latest.
This is really simple.
I have a TableLayoutPanel that is populated with controls (just Labels, Buttons, and some Panels with buttons) based on a database query. When the data needs to be refreshed, I use TableLayoutPanel.Controls.Clear(). Unfortunately, this is a very slow operation. I would expect it to be faster than the code populating the table, but it is at least 3 or 4 times slower.
I definitively proved that the slowness is when executing Controls.Clear() by executing this as the single thing done to the TableLayoutPanel after a message box is displayed (then the procedure returns). The controls visibly disappear from the bottom up. When the recordset is used to repopulate the TableLayoutPanel, the speed of the controls appearing from top to bottom is almost faster than I can see.
I'm already doing TableLayoutPanel.SuspendLayout() and ResumeLayout().
Using this.DoubleBuffered = true on the form doesn't appear to do anything.
I could just Dispose the entire control and recreate it through code, but this is a big pain and makes having a nice form designer GUI pointless. I would have to dig into every property I've set on the control and create a line of code for it (though I guess I could get this out of the designer code itself, it still feels wrong).
Any ideas on how to do the job faster? I'm even open to using other methods besides a TableLayoutPanel... I just need the freedom to put multiple buttons per cell or barring that to be able to span columns in the table header.
Can C# at least freeze the whole form while it redraws and then paint all at once?
I've run into issues with slowness using TableLayoutPanels as well. Rather than setting the DoubleBuffered property on the form, the best solution I have found is to create a new class that inherits from TableLayoutPanel, and in that class' constructor, enable double-buffering:
public class DoubleBufferedTableLayoutPanel : TableLayoutPanel
{
public DoubleBufferedTableLayoutPanel()
{
DoubleBuffered = true;
}
}
Then, use the DoubleBufferedTableLayoutPanel wherever you would normally use a TableLayoutPanel.
This seems to work for my uses:
tableLayoutPanel.Visible = false;
tableLayoutPanel.Clear();
/* Add components to it */
tableLayoutPanel.Visible = true;
There is no need to subclass TableLayoutPanel as in Chris Ryan's answer. I had the same problem and solved it by setting the property through reflection:
typeof(TableLayoutPanel)
.GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.SetValue(myTableLayoutPanel, true, null);
If i'm going to built up some dynamic gui i'm always going to do so in code. But at a starting point i just start with the designer on a dummy form and style each control the way i (or better the customer) like(s). Afterwards i take a look into the Designer.cs file and copy the necessary property settings out of it into some factory function like
private TextBox CreateTextBox(string name, /* maybe other parameters */)
{
var textBox = new TextBox();
textBox.Name = name;
//Other settings from given parameters...
//Further settings which are all the same for these kind of control
textBox.KeyDown += (sender, e) => {};
return textBox;
}
So i make sure that every control feels and looks the same on my GUI. This will be done on each level within my surface (starting with the small controls like TextBox and goes up to the containers like GroupBox or TableLayoutPanel.
In some cases this leads to a point where a factory function calls several other factory functions. If this is becoming true it's time to think about encapsulating these controls into a single UserControl, but as always it depends if this is needed or not.
From my side i can only encourage you to move your code out of the designer into a self-written function. At the beginning it is (as always) more work, but afterwards it is easier to make even bigger changes to the layout.
I have a user control that contains a 2-column TableLayoutPanel and accepts commands to dynamically add rows to display details of an item selected in a separate control. So, the user will select a row in the other control (a DataGridView), and in the SelectedItemChanged event handler for the DataGridView I clear the detail control and then regenerate all the rows for the new selected item (which may have a totally different detail display from the previously selected item). This works great for a while. But if I keep moving from one selected item to another for quite a long time, the refreshes become VERY slow (3-5 seconds each). That makes it sound like I'm not disposing everything properly, but I can't figure out what I'm missing. Here's my code for clearing the TableLayoutPanel:
private readonly List<Control> controls;
public void Clear()
{
detailTable.Visible = false;
detailTable.SuspendLayout();
SuspendLayout();
detailTable.RowStyles.Clear();
detailTable.Controls.Clear();
DisposeAndClearControls();
detailTable.RowCount = 0;
detailTable.ColumnCount = 2;
}
private void DisposeAndClearControls()
{
foreach (Control control in controls)
{
control.Dispose();
}
controls.Clear();
}
And once I get finished loading up all the controls I want into the TableLayoutPanel for the next detail display here's what I call:
public void Render()
{
detailTable.ResumeLayout(false);
detailTable.PerformLayout();
ResumeLayout(false);
detailTable.Visible = true;
}
I'm not using anything but labels (and a TextBox very rarely) inside the TableLayoutPanel, and I add the Labels and TextBoxes to the controls list (referenced in DisposeAndClearControls()) when I create them. I tried just iterating over detailTable.Controls and disposing them that way, but it seemed to miss half the controls (determined by stepping through it in the debugger). This way I know I get them all.
I'd be interested in any suggestions to improve drawing performance, but particularly what's causing the degradation over multiple selections.
Just use a custom control that inherits from TableLayoutPanel and set the DoubleBuffered property on true, works great... especially when you dynamically add or remove rows.
public CustomLayout()
{
this.DoubleBuffered = true;
InitializeComponent();
}
I had a similar issue with TableLayout. If I used TableLayout.Controls.Clear() method, the child controls never got disposed but when I simply dropped the TableLayout without clearing it, the leak stopped. In retrospect, it's funny I used the Clear method to prevent some kind of leak.
Apparently, Clear method does not explicitly dispose of the controls (which makes sense, because the fact that you removed them from the TableLayout does not mean you are done with them) and removing the child controls from the TableLayout prevents the cleanup routine to dispose of the children when the LayoutTable itself gets disposed (it simply does not know about them anymore).
My recommendation: Delete the detailTable.Controls.Clear(); line, remove the detailTable itself from the parent's Controls collection and dispose it, then create a brand new TableLayout for the next round. Also lose the DisposeAndClearControls method entirely since you won't need it. In my experience, it worked nicely.
This way, you won't have to recycle your entire user control anymore but only the TableLayout within.
Unfortunately, the only advice I can offer is to take care of the placement of your controls yourself. In my experience the .NET TableLayoutPanel, while very useful, is leaking SOMETHING and becomes unusably slow as it grows (and it doesn't take an unreasonable number of cells to get to this point, either). This behavior can be seen in the designer as well.
I changed the containing form to just construct a new version of my user control on each selection change. It disposes the old one and constructs a new one. This seems to perform just fine. I'd originally gone with reusing just one for performance reasons anyway. Clearly that doesn't improve the performance. And the performance isn't a problem if I dispose the old one and create a new one.
Unfortunate that the TableLayoutPanel leaks like that, though.
I faced the same problem and found a good way without changing too much:
in VB.net
Dim tp As Type = tlpMyPanel.GetType().BaseType
Dim pi As Reflection.PropertyInfo = _
tp.GetProperty("DoubleBuffered", _
Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.NonPublic)
pi.SetValue(tlpMyPanel, True, Nothing)
or in C#:
Type tp = tlpMyPanel.GetType().BaseType;
System.Reflection.PropertyInfo pi =
tp.GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
pi.SetValue(tlpMyPanel, true, null);
TableLayoutPanel.Controls.Clear() works fine for me, maybe its because i clear it from a different tab than its displayed in.
List<Control> controls = new List<Control>();
foreach (Control control in tableLayoutPanelEnderecoDetalhes.Controls)
{
controls.Add(control);
}
foreach (Control control in controls)
{
control.Dispose();
}