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();
}
Related
I want to load Form's controls to a panel in C# so the panel will show the same components as the form. I have tried this code:
foreach (Control control in (new Form2()).Controls)
{
panels[panelsCounter].Controls.Add(control);
}
But the problem is that when I'm running the program it loads only the type of control that I've added last (For example if I've been added a label and than I've added a button to the form it shows only a button, but if I add another label, it shows both of the labels, but not the button).
Please help me.
This is a classic bug, you are modifying the collection while you are iterating it. The side-effect is that only ever other control will be moved to the panel. You'll need to do this carefully, iterate the collection backwards to avoid the problem:
var formObj = new Form2(); //???
for (int ix = formObj.Controls.Count-1; ix >= 0; --ix) {
panels[panelsCounter].Controls.Add(formObj.Controls[ix]);
}
Controls are not designed to be displayed multiple times. You cannot add controls to multiple forms, or add the same control to a form multiple times. They simply weren't designed to support it.
You could go through each control and create a new control of the same type, and even copy over the values of their properties (or at least what's publicly accessible to you), effectively cloning them, but it's important that it be a different control that you add to the new panel.
I have a tree of Visuals. The main window has a full-sized Canvas. The Canvas holds the root of some custom Visuals. The first custom Visual (A) can expand and contract based on what the user is viewing, which are in children (Bs, that are DrawingVisuals). As the main window resizes, I want to add children to or remove children from A. The same may happen when the user scrolls any direction in the view. When children (Bs) are added, I want them to be rendered. (The Bs that are removed are no problem because they are no longer visible anyway.)
Take a scenario: The main window is 640x480. I resize to twice that size, which would reveal more Bs, which then are added to A. So far I cannot get the newly added Bs to render, not even when using Invalidate???() methods of the FrameworkElement. I have tried Invalidate???() in the main window and in the A when Bs are added. (I have not with the Bs, figuring they are new.) It seems like Invalidate???() is not the way to go.
I have found a hack that allows the new Bs to render on resize. In the main window I can remove the A from the Canvas and add it back, which forces the tree to be rerendered:
this.contentCanvas.Children.RemoveRange(0, contentCanvas.Children.Count); // remove root
this.contentCanvas.Children.Add(this.CustomVisual_A); // readd root
For obvious reasons this is not desirable, especailly when scrolling. Specifically, the Bs that are being added and removed are managed in that way because they are quite large, and I need to reduce the memory footprint and increase rendering speed as much as possible. That's why I am going to the trouble.
Can someone help me understand what I need to do to get the newly added Bs to render? Thanks much!
I solved this. Even though the FrameworkElement is custom, you need to use the standard AddVisualChild() and RemoveVisualChild() methods to managed your children:
this.AddVisualChild(new B());
...
this.RemoveVisualChild(existingB);
You still maintain your own collection of Bs, but it seems that unless you use AddVisualChild() and RemoveVisualChild() the underlying FrameworkElement doesn't see the change (actually Panel, the super class of Canvas).
This may also involve the parent in notification in some way; not sure. Ultimately these collection management methods cause the OnVisualChildrenChanged() method, defined on Panel, to be invoked, so it should be implemented in the A. At that time I add/remove the child to/from my internal collection, and then invoke on to base:
protected override void OnVisualChildrenChanged(DependencyObject added, DependencyObject removed)
{
if (added != null)
{
this.children.Add((B) added);
}
if (removed != null)
{
this.children.Remove((B) removed);
}
base.OnVisualChildrenChanged(added, removed);
}
This causes some internal indication that rendering is necessary.
I figured something like that had to be occurring because removing A from my custom Canvas and re-adding the A forced the render.
There are a few calls for this...
element.UpdateLayout();
element.InvalidateArrange();
element.InvalidateMeasure();
element.InvalidateVisual();
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 !!!
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.
My question is simple: how bad is the following snippet of code? How would you do it?
CancelEventHandler _windowClosing;
private CancelEventHandler WindowClosing
{
set
{
clearEventHandlerList();
this.Closing += value;
_windowClosing = value;
/*
* if calling the method with null parameters,
* it will set up itself as the primary control on the Window
*/
_windowClosing(null,null);
}
get
{
return _windowClosing;
}
}
private readonly CancelEventHandler[] CONTROLS = null;
private int current = 0;
public InitializerForm()
{
InitializeComponent();
/*
* these are the handlers for the different controls,
* in the order of appereance to the user
*/
STATES = new CancelEventHandler[] { handler1, handler2, etc. };
WindowClosing = CONTROLS[0];
}
private void clearEventHandlerList()
{
foreach (CancelEventHandler c in CONTROLS)
{
this.Closing -= c;
}
}
private void handler1(object obj, CancelEventArgs e)
{
if (obj == null)
{
//hide every other control, but this one, also set up if necessary
}
else
{
//do something
WindowClosing = CONTROLS[++current]; // set the next control to show
e.Cancel = true;
}
}
The point would be that the code wouldn't close a form, but instead show another component on it, and the set the way to handle that (this is mobile platform, so clicking OK button on the top generates a closing event). This is because showing several forms (4 or 5) one after another to the user makes the app blink, and also very annoying, while replacing just components is much smoother. This model works, but seems very nasty, and I would like a cleaner way to handle this.
Update:
I updated the code sample so that variable names are somewhat speaky. Still, I'm convinced this is awful, (a) but not sure how much, and more importantly, (b) how to do it better.
Update 2:
So, it seems that the code is still a bit mysterious.
Now here's what the problem is:
I show the user a form, which instructs him what to do in several languages. He proceeds by clicking OK on the window. Next, I ask for his language, and then a few questions (where his/her GPS is, etc.) like this. After he could answer the questions (this shouldn't take more than a few seconds each), I show him a splash screen (I load stuff in a separate thread meanwhile), which has a picture. Showing these forms one after another makes the whole application start slow, and filled with UI lags.
Here's what I do to work around the lags: I put the content of the windows into panels, and put those panels one on another, and hide every one of them but the one that should be visible to the user. (current variable) Each of the windows does different things, so I need to change handler of the window closing event in addition. In this code the part which enables the panel is in the same function (handler1, handler2, etc.) with the part which handles the window closing event. If the arguments are null, it does the former, if it isn't (that means it was triggered by the user) it does the latter.
I need an extensible solution to this so that I can insert and remove dialogs anytime I want (the order and the pointers to the functions are stored in the CONTROLS field, and this seems to be very convenient, if you actually understand it. Although it is never easy to change the entire content of a form, there ought to be a simpler way to do this, as well a nicer one, that is what I'm looking for.
I hope this time I could explain how the model works.
I think it might be theoretically possible to make that code more delightfully diverting, perilously puckish, jovially jeopardous, cheerily chancy and unwarily whimsical but it would require some serious thought.
somehow your code makes me want to cry, i´m sorry. i read it twice and all i know about it is that it "doesStuff" with "STATES".
if you really want some help on this one you will have to work on it yourself first...
Use, XML! It's human-readable!
More seriously-
It seems like you're trying to create some sort of configuration wizard, so I'd start by researching that. Regarding your particular solution, I generally advocate very strongly against the "layered panel" approach. I do so because I maintain apps written by people who found this approach, or the related "hidden tabs on a tab control" approach, to be a good idea. It's not, and maintainers will curse your name for years to come.
That being said, what alternatives are there? Well, one alternative is what you've already dismissed because of its "flicker". I'd say that, in general, the flicker isn't that big of a deal for a quick and dirty application. It might be a good idea to make sure that your new window is called up before closing the old one, for example. (I'm assuming this is possible, I haven't developed on a mobile device.)
Another possibility might be a less-evil version of your layered panels. Instead of throwing a half-dozen panels into one form, create a separate user control for each wizard page and then add/remove the user controls to a containing form. This can avoid your flicker and will prove to be much easier to maintain because each page is in a different control. This might also ease any subsequent "Back" button functionality and make your data structures more naturally defined because those user controls will be associated with a specific logical bit of data. It's still not ideal, but it's probably good enough for a one-off solution.
A third technique, if you foresee extensive wizard modification as the product matures, might be to generalize the creation of your user controls by defining them in a more logical/declarative manner (e.g. via XML). If you dynamically generate sensible controls based on XML, then modifying the panels might be as easy as diving into your XML and doing something like:
<Questions>
<Question type="Text"> <!-- generate a textbox for the answer field -->
Favorite Color:
</Question>
<Question type="Number" range="0-255"> <!-- Maybe this is a spinner -->
The answer to life, the universe, and everything:
</Question>
</Questions>
That's just off the top of my head, and completely overkill for any one-off application, but it's a possibility.
Now, let me caveat this by saying this might work, but it may not be the answer to your real problem - that of a slow and unresponsive UI when you have a lot of forms. The real answer may be to just go ahead and do all separate forms, but have each form load its child forms in a background thread while the user is staring at the first form.
But assuming you're still set on this, I'd start off by making a separate class just to handle the Panel stacking/hierarchy. Call it PanelManager. You would instantiate the PanelManager and associate it with the main form, then add Panels to it (perhaps keyed to a String) and set the order. In the main form, have the closing handler call PanelManager.CloseCurrentPanel() and if there are no more Panels to show then it's time to close the main form.
Time for pseudo-code! Here's a quick idea for the class, i'll leave it to you to implement it:
public class PanelManager {
// constructor
public PanelManager (Form ownerForm);
// short-cut properties
public Panel this[int idx]
{ get; set; }
public int Index
{ get; set; }
// main functionality
public int AddPanel (Panel p);
public void SetPanelOrder (Panel p, int idx);
public void RemovePanel (Panel p);
public void RemovePanelAt (int idx);
// shows the first Panel
public void Show ();
// shows Panel[idx]
public void Show (int idx);
// adds the panel to the top of the stack and displays it
// returns the index of the panel
public int AddPanelAndShow (Panel p);
// hides the current panel, displays the one underneath it
// returns false if there are no more panels
public bool HideCurrentPanel ();
}
in the constructor for the main form, instantiate it by new PanelManager (this), then in the closing event handler, call panelManager.HideCurrentPanel () and then figure out whether or not you need to close it after that.