The section lists out common performance problems and
suggested solutions.
Changing the UI in OnLoad
Problem: Changing properties such as Bounds, Size,
Location, Visible and Text/Image/etc for AutoSized controls InitializeComponent
and/or Suspend/ResumeLayout can cause perf problems. Each time one of these
properties are touched a layout is forced. Changing any of these in Form.Load
is particularly bad, at that point the handles have all been created, and
window messages have to be sent to change size/locations.
Solution: Add a Suspend/Resume Layout to prevent
extra layouts from occurring. If possible, make all of these changes within
InitializeComponent – this way only one layout is ever needed.
Visual Inheritance
Problem: The code generated for visual inheritance
(class Form2 : Form1) is actually not that efficient, as the constructor for
the base form executes, then the constructor for the derived form executes.
This essentially forces two layouts as ResumeLayout is called on the form
twice. Visual Inheritance also has problems for localization, as the
localization tools dont work well with this kind of situation.
Solution: For perf reasons, if you can avoid using
it, this is the best solution. If you must, you may want to consider using a
base form and swapping in a panel with the derived controls as needed.
Setting the Size/Location of child controls in the Resize event.
Problem: Using the Resize event to size/position
child controls circumvents the Suspend/Resume Layout perf protection.
Solution: Use the Layout event to size/position child
controls.
private void button1_Click(object sender, EventArgs e) {
// Inefficient Layout practice
this.Resize += new System.EventHandler(this.Form1_Resize);
this.SuspendLayout();
this.Size = new Size(500, 500); // SuspendLayout circumvented!
this.ResumeLayout(false);
this.Resize -= new System.EventHandler(this.Form1_Resize);
// Efficent Layout pracitce
this.Layout += new LayoutEventHandler(Form1_Layout);
this.SuspendLayout();
this.Size = new Size(500, 500);
this.ResumeLayout(false);
this.Layout -= new LayoutEventHandler(Form1_Layout);
}
void Form1_Layout(object sender, LayoutEventArgs e) {
// for added performance, consider storing off the last size
// that happened when we got here and only perform the layout if the size has changed.
Rectangle bounds = this.ClientRectangle;
bounds.Inflate(-10, -10);
outerPanel.Bounds = bounds;
this.Text = "Resized in LAYOUT!";
}
private void Form1_Resize(object sender, EventArgs e) {
Rectangle bounds = this.ClientRectangle;
bounds.Inflate(-10, -10);
outerPanel.Bounds = bounds;
this.Text = "Resized on RESIZE!";
}
Using nested AutoSized table layout panels more than two deep.
Problem: The tablelayoutpanel is a great control for
automatically controlling layout, however it has its price performance wise.
The simpler you can make the layout, the faster it will be.
Solution: Keep the complexity of your layout to a
minimum. If the flowlayoutpanel can achieve the same results for your UI,
consider tatically using that in places. A flow is always mathematically
cheaper to calculate than a table.
Setting the Font after InitializeComponent is complete.
Problem: The font controls the auto-scaling mechanism
for the form. Setting this after the form has finished laying out will cause it
to perform an expensive layout all over again, changing the size/location of
child controls.
Solution: If it knows this data before the final
resume layout of initialize component, it will have all the data it needs to
perform the layout efficiently, ONCE.
Dynamically filling in data
Problem: Changing the text of child controls can
cause layouts.
Solution: Call SuspendLayout on the parent control
first, then set the text of all the controls, then call ResumeLayout. If this
can be done before the handles are created, it will be even faster.
Individually setting the Size/Location of a control in multiple
properties.
Problem: There are several properties in which you
can change the size/location of a control. These include, but are not limited
to: Width, Height, Top, Bottom, Left, Right, Size, Location, Bounds, and ClientSize.
Setting panel1.Width = 10 and panel1.Height = 10 causes twice the work to occur
than setting them both together (especially after the handle has been created
as windows forms is chatting live with the operating system about what the size
should really be.)
Solution: Do all your calculations, then set the
property that reflects the most information you have. If you're just chaning
the Size, set Size, if you're changing the Size and Location change Bounds.
private void button1_Click(object sender, EventArgs e) {
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 1; i < 1000; i++) {
panel1.Width = i;
panel1.Height = i;
}
sw.Stop();
Stopwatch sw2 = new Stopwatch();
sw2.Start();
for (int i = 1; i < 1000; i++) {
panel1.Size = new Size(i, i);
}
sw2.Stop();
MessageBox.Show(String.Format("Trial 1 {0}\r\nTrial 2 {1}", sw.ElapsedMilliseconds, sw2.ElapsedMilliseconds));
}
// As expected, the second one takes about 1/2 the time because there's half the conversation going on.
// Trial 1 3717
// Trial 2 1865
Clearing a controls collection to swap views
Problem: Someone has a form which has a panel which
swaps in and out. When swapping panels, Controls.Clear() is used to clear off
the old panel.
This is inefficient as it:
- Forces a layout - the Panel's parent is set to null, which causes
a ParentChanged, which results in a layout
- Leaks resources – as the Panel is no longer associated with the
form and will hold open USER32 handles.
Solution: There are a couple of solutions, depending
if you're done with the panel or not.
- If you're not done with the panel – just set it to be
Visible=false
- If you're done with the panel – call dispose on it.