Suggestions for making your managed dialogs snappier


Diagnosing Performance Problems with Layout

 

So you’ve built up your complex form, and starting it or resizing it is slightly more exciting than watching paint dry.   It could very well be that the way everything is hooked up, the dialog is fighting layout rather than working with it.  If you’re not familiar with what layout is and how it works, read this first.

 

The first question to ask is, is the dialog just performing layout way too many times?    The best way to determine this is to add in some Debug.WriteLines and then analyze the output to see why layout is occurring and if you could prevent it by batching up some property sets under a SuspendLayout/ResumeLayout.

 

       public Form1() {

            InitializeComponent();

            SnapAllLayout(this);

        }

 

        private void SnapAllLayout(Control start) {

 

            start.Layout += new LayoutEventHandler(snap_Layout);

            foreach (Control c in start.Controls) {

                SnapAllLayout(c);

            }

        }

 

        void snap_Layout(object sender, LayoutEventArgs e){

                Control c = sender as Control;

                System.Diagnostics.Debug.WriteLine(String.Format("Control: {0}\r\nBounds: {1}\r\nReason {2}\r\n Where {3}",

                    c.Name,

                    c.Bounds,

                    e.AffectedProperty,

                    new System.Diagnostics.StackTrace().ToString()));

 

         }

 

         void Form1_Layout(object sender, LayoutEventArgs e) {

            MessageBox.Show("Form1_Layout!");

         }

 

         void Panel1_Layout(object sender, LayoutEventArgs e) {

            MessageBox.Show("Panel1_Layout!");

         }

A note about SuspendLayout and ResumeLayout

 

SuspendLayout and ResumeLayout only prevent OnLayout from being called.  Additionally they only prevent OnLayout from being called for that particular control.  So if you have a Form with a Panel in it, and call SuspendLayout on the Form, the Panel's layout is not suspended.

 

       // simple example showing how suspending on the parent does

       // not suspend the child control

       private void button1_Click(object sender, EventArgs e) {

            this.Layout += new LayoutEventHandler(Form1_Layout);

            panel1.Layout += new LayoutEventHandler(Panel1_Layout);

       

            // Test one - calling PerformLayout here does not call Form1_Layout

            this.SuspendLayout();

            this.PerformLayout();

            this.ResumeLayout(false);

 

            // Test two - calling PerformLayout here calls Panel1_Layout

            // Child controls are not suspended when the parent is suspended.

            this.SuspendLayout();

            panel1.PerformLayout();

            this.ResumeLayout(false);

 

            // Test three, properly suspending layout

            this.SuspendLayout();

            panel1.SuspendLayout();

            panel1.PerformLayout();   // <--- Layout event on Panel NOT called

            panel1.ResumeLayout(false);

            this.ResumeLayout(false);

 

            panel1.Layout -= new LayoutEventHandler(Panel1_Layout);

            this.Layout -= new LayoutEventHandler(Form1_Layout);

        

        }

 

What properties/methods cause layout?

 

The "reasons" for layout that are passed to LayoutEventArgs.AffectedProperty usually match the property name that has been set.

 

Additionally, changing the control collection can cause a layout.  Methods that do this include but are not limited to:

       control.Controls.Add(..)

       control.Controls.Clear()

       control.Controls.Remove and RemoveAt

       control.BringToFront()

       control.SendToBack()

       control.Controls.SetChildIndex

       control.Parent

 

Inefficient layout practices

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 don’t work well with this kind of situation.

 

Solution:  For performance 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.

 

       // simple example to show the difference between using

// the Resize event versus using the Layout event

        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!";

        }

 

 

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.

 

Setting the Size/Location of a control in multiple property sets

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.

 

       // simple example to show performance difference between

// setting width and height individually versus setting

// size

        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));

        }

 

       // If you run this, the second one takes about 1/2 the time because

       // there's half the conversation going on. 

Summing up

These are suggestions that have helped folks solve their dialog problems in the past.  As always measure first, then fix, then measure again.  If you are tuning your application, check out these articles:

Writing High-Performance Managed Applications : A Primer

Writing Faster Managed Code: Know What Things Cost

Garbage Collector Basics and Performance Hints

How To: Use CLR Profiler

How, When, Where and Why to use Dispose

 

Comments (8)
  1. Daniel Moth says:

    Blog link of the week 09

  2. joeycalisay says:

    >Setting the Size/Location of a control in multiple property sets:

    I agree, it is interesting to note that property sets for Control’s Top, Left, Location, Height, Width, Size redirects to their corresponding SetBounds method with the proper BoundsSpecified enumeration.

    >Visual Inheritance

    In addition, Anchored controls are also messed up down the inheritance tree with the generated SuspendLayout/ResumeLayout method calls for container controls.

    >If you must, you may want to consider using a base form and swapping in a panel >with the derived controls as needed.

    This statement was kind of vague for me, :p.

  3. JFo says:

    >Setting the Size/Location of a control in multiple property sets:

    >I agree, it is interesting to note that property sets for Control’s Top, Left, Location, Height, Width, >Size redirects to their corresponding SetBounds method with the proper BoundsSpecified >enumeration.

    Yes, however you do need to be careful if you calling SetBounds directly, the BoundsSpecified has meaning under the covers as to what dimensions of the bounds you really wanted to set… For example a layout engine laying out its children would call with BoundsSpecified.None, so that when the control is moved to another layout it snaps back to the size the developer has set. You can see this when you Dock.Fill a control, then set its size to 100,100. Then set it back to Dock.None.

    >Visual Inheritance

    >In addition, Anchored controls are also messed up down the inheritance tree with the generated >SuspendLayout/ResumeLayout method calls for container controls.

    Yes, there are lots of problems in this area. In Whidbey we’ve played with the designer generated code to try to help this problem out a bit more. ResumeLayout(false) is a way of recalculating the anchor distances, I believe Everett inherited forms did not have this (although I am recalling this from the top of my head – dont hold me to it.)

    >If you must, you may want to consider using a base form and swapping in a panel

    >with the derived controls as needed.

    > This statement was kind of vague for me, :p.

    Intentionally so, dont want to create a flame war over this. Folks tend to believe that visual inheritance is good design because it fits in well with object orientation. However in practice, you’re not really sure what effects the settings in the base are going to have on the settings in the derived class.

    The suggestion is to have a base form like before, but have replacable pieces of it. For example, if all your forms look the same except for the middle bit (like a wizard framework would have) just have the middle be user controls that can be swapped in. This becomes easier in Whidbey, as you can have a TableLayoutPanel, and just replace the contents of one cell by the various panels. Folks have done this in several places in Whidbey dialogs, and it has worked out really well. The localization team loves it because the user controls are openable in WinRes and the main outer form is openable in WinRes.

  4. joeycalisay says:

    >Yes, however you do need to be careful if you calling SetBounds directly, the BoundsSpecified has meaning under the covers as to what dimensions of the bounds you really wanted to set… For example a layout engine laying out its children would call with BoundsSpecified.None, so that when the control is moved to another layout it snaps back to the size the developer has set. You can see this when you Dock.Fill a control, then set its size to 100,100. Then set it back to Dock.None.

    Thanks for this info.

    >ResumeLayout(false) is a way of recalculating the anchor distances, I believe Everett inherited forms did not have this (although I am recalling this from the top of my head – dont hold me to it.)

    inherited forms will have resumelayout calls if they do have changes on inherited controls or new controls on them, if i get what u mean. btw: what is everett inherited forms?

  5. JFo says:

    >btw: what is everett inherited forms?

    A project created using the v1.1 (Everett) designer.

  6. jfo's coding says:

    Believe it or not *absolutely* nothing.&amp;nbsp; One method calls the other.&amp;nbsp; However because the latter…

  7. jfo's coding says:

    Custom PaintingPainting best practices ComboBox OwnerDrawLayoutDock layout/Using the Splitter control…

  8. jfo's coding says:

    Custom PaintingPainting best practices ComboBox OwnerDrawLayoutDock layout/Using the Splitter control…

Comments are closed.

Skip to main content