Visual Inheritance Revisited
- V...I.... What's the "V" stand for?
- What's the --
- 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:
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();
...code for the controls...
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 169);
this.Name = "Form1";
this.Text = "Form1";
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
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
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
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
- Enter Constructor
- Exit Constructor
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
- Save everything, close all windows
Form2again (as described above), but don't recompile.
- Close the designer window, open the code window
- Remove the
- Save and close the window
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
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
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...