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 classForm1
- Exit Constructor
Form1()
- Call
InitializeComponent()
of classForm2
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()
andResumeLayout()
statements fromInitializeComponent()
in classForm2
. - 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 classForm2
), 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...