Visual Inheritance Revisited

        - V...I.... What's the "V" stand for?
        - Visual.
        - What's the --
        - Inheritance.
        - Ohhhhh........ What was the "V" again?

My recent article about visual inheritance generated a lot of positive feedback (at least what I would call "a lot"). In this post, I'll try to answer some the questions I received via email.

Question: How can I reproduce the problems you describe? My first quick test ran without problems...

Create a new Windows Forms application, and add two labels and a button to the form "Form1" (setting the background colors of the labels makes it easier to see what is happening later). Arrange the controls on the form as shown in the following screenshot:

Now anchor the controls:

  • label1: Top, Left, Right
  • button1: Top, Right
  • label2: Top, Bottom, Left, Right

Compile the project, and add an inherited form ("Form2"). The form will look like this:

Set the Text property to "Form2". Resize the form and add a button ("button2"):

Anchor the button to Top, Right, then compile again.

Be prepared for a surprise: after the project is compiled, you get something like this...

Question: What's the reason for this odd behavior?

Let's take a look at the code of the InitializeComponent() method of base form:

private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();

    ...code for the controls...

    //
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 169);
this.Controls.Add(this.label2);
this.Controls.Add(this.button1);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}

Obviously, this is generated code. When speaking of "serialization" of objects, people usually think of serializing an object to binary data or XML; in this case the object (instance of class Form1) is serialized to code. This code contains everything that is required to create the layout and the controls of Form1 exactly the way it was specified in the forms designer. Every property is set exact to the required value, e.g. if a panel is docked, not only is the Dock property set, but also the Location and Size. So events are neither necessary nor wanted (because of their sheer quantity when settings properties like like e.g. Size for each control). This is why basically everything except the creation of the control instances is kept inside the lines

    this.SuspendLayout();

and

    this.ResumeLayout(false)

The call to SuspendLayout() temporarily suspends the layout logic, ResumeLayout() turns it back on. The argument false tells the form not to handle pending layout requests, because there's no need to - everything is already laid out as it was designed. So far, everything is ok.

Now, what's happening inside the derived class Form2?

In each form class, the constructor of the class calls the method InitializeComponent(). This method is private, so each class has its own method. When an instance of class Form2 is created, the order of execution is

  • Enter Constructor Form2()
  • Enter Constructor Form1()
  • Call InitializeComponent() of class Form1
  • Exit Constructor Form1()
  • Call InitializeComponent() of class Form2

In the moment the constructor of the base class is left, the layout and the controls are perfect for a Form1 instance - but that's not what we want. The method InitializeComponent() of class Form2 contains the code that is necessary to achieve the desired layout by tweaking the base form and adding the second button. While setting the Text property to "Form2" is rather trivial, setting the ClientSize property causes serious trouble. The problem is that the size is set while the layout logic is suspended; i.e. the size of the form is changed, but the anchored elements of the base class are not updated.

If you're interested, try the following:

  • Remove class Form2
  • Save everything, close all windows
  • Recompile
  • Create Form2 again (as described above), but don't recompile.
  • Close the designer window, open the code window
  • Remove the SuspendLayout() and ResumeLayout() statements from InitializeComponent() in class Form2.
  • Save and close the window
  • Recompile

If you now open Form2 in the designer, the form will be fine. But if you change anything (e.g. move the second button) and recompile, the form will be scrambled again. This is because the code of InitializeComponent() was generated again - including the SuspendLayout() and ResumeLayout() statements. By the way: if you don't put a control on the derived form, the two statements are not generated, and thus the derived form is displayed as expected. This explains why some people at first couldn't reproduce the effects I described in the other article.

Question: So why does docking work, but not anchoring?

  • Docking tells a control to grab as much space as possible (for top/bottom/left/right: towards to the specified direction) until a border of the container is reached. When the size of the control's container changes, the border is moving, so the docked control will resize itself accordingly.
  • Anchoring tells a control to keep a constant distance between a specified border (top/bottom/left/right) of the control and the corresponding border of the container at all times. Now if the container is resized while layout is suspended (which is the case in InitializeComponent() of class Form2), the anchored control does not notice the change. When layout is resumed, the control will keep the distance that exists at this time. This can be observed if you resize the form either inside the forms designer or at run time. The anchoring works perfectly, but unfortunately using the wrong offsets...

1 Comment

Comments have been disabled for this content.