Visual Inheritance - How to escape Layout Hell

Visual inheritance (VI) is a very powerful feature; unfortunately, some of the more tricky bits are not that well-documented. A lot of things can go terribly wrong (like controls moving themselves to unexpected locations), as I had to learn the hard way in the last weeks. But if you follow a few simple rules, VI does work reliably.

First a short introduction for those who haven't looked into VI yet (actually, it took me some time to really discover VI). In C# (and other .NET languages) forms and controls are no longer "magic" things (like e.g. in VB6) but simply classes derived from special base classes (e.g. System.Windows.Forms.Form). Such a class can in turn be used as the base class for another class; if a base form contains controls, the derived form inherits them. Again, there's nothing magical to this all; when looking at the code (or better: single stepping through it), it's pretty obvious how VI works. The constructor of each form/control class contains a call of a private method "InitializeComponent()". If B is derived from A, then the constructor of A is called first (thus "InitializedComponent()" of A), then the constructor of B (and "InitializeComponent()" of B).

VS.NET directly supports VI, i.e. it is possible to edit a derived form (or user control) in the windows forms designer. By default, "editing" a derived form means adding additional controls. This is because controls added to a form are marked as "private", i.e. they are not accessible from a derived class. Again, as we're simply dealing with classes, it's just a matter of changing the access specifier to e.g. "protected". Inside the forms designer, this can be done by changing the control's "Modifiers" property.

Here's a typical example for visual inheritance: Imagine you want to have a nice looking header on all of your dialog windows, maybe something like this:

With VI, you define a base class for all your dialogs, containing the header (a panel docked to the top, with a picture box for the image and a label for the text). You then define two properties on the form for setting the icon and title text:

public string TitleText
{
get { return m_lblTitle.Text; }
set { m_lblTitle.Text=value; }
}
public Image TitleImage
{
get { return m_picTitle.Image; }
set { m_picTitle.Image=value; }
}

If you derive a form from this base class, your derived form will automatically feature the header defined in the base class; using the two properties, the header can be customized. Cool, huh?

This is usually the point where articles describing visual inheritance stop, leaving out some of the really important questions:

Question: How do I make sure the controls on the derived form are positioned correctly if the size of the header changes?

Define a panel on the base form below the header and make it accessible from the derived form by setting its access specifier to e.g. "protected". Set "Dock" to "Fill". In your derived form, you drop your controls into this panel. If the size of the header changes, the controls will be repositioned automatically, as they are positioned relative to the upper left corner of the panel they live in.

DON'T position the panel manually and set the anchor to "Top, Bottom, Left, Right". This will not work correctly if the size of the derived form differs from the size of the base form.

Question: But I want to define specific areas for the controls on the derived form - how do I do this without using anchors?

Use nested panels and the "DockPadding" property. The following image shows a base form with two panels to be used in derived forms (panels #3 for buttons like OK and Cancel, and #5 for all other controls):

 

Of all the panels, only #3 and #5 are set to "protected", the remaining are set to "private".

Question: What is so bad about anchors?

As a rule of thumb, use anchors other than "Top, Left" only on forms/controls that are not used as the base for other forms/controls. This is because the layout of anchored controls will not be updated if the size of the derived form/control is changed in the "InitializeComponent()" method of the derived class. If the form is resized later, things are Ok. This is one of the reasons why this problem sometimes remains unnoticed until some time later, when suddenly VI seems to be broken.

Question: But I can fix it by making the controls "protected"?

Yes and no. First of all, if form A contains a control X that is anchored, and form B is derived from A and has a different size, then setting X to "protected" fixes the problem only for B, but not for a form C derived from B. And under some circumstances there still seem to be some problems left even in B (I didn't invest too much time into researching this, though)

From a OOD point of view, making lots of controls accessible from derived classes only to fix layout problems isn't such a good idea, anyway. If your base form contains e.g. a list, then it's better to offer only the concept of a list through properties, methods and events, but not the actual listbox control. If you decide to switch to a dropdown list or a listview at a later time, you don't have to touch the code in the derived classes.

Question: How do I position OK and Cancel buttons in the lower right corner without a gazillion nested panels?

A lot of layout can be done with surprisingly few panels. Of the panels shown in the image shown above, the divider panel is actually not necessary (I just added it to make things a bit easier to understand in the first step). The secret is the right combination of docking and dock padding. Take a look at this:

The buttons are 23 pixels high, the padding at the top is 8 pixels, so the height of the panel was set to 31 pixels.

Question: But what about more complex layouts?

From my own experience, if you have a base form with a complex layout, something already went wrong in the (non-visual) design of your code. Note the word "base": On a normal form e.g. for data entry, a complex layout is nothing unusual or even bad. But if you have a base form, you want to reuse something on that form - if this "something" is too complex, it should be encapsulated in a user control. User controls are a powerful tool for reducing the complexity of layout and code. Most layouts can be broken down to a few user controls which in turn can be positioned easily using docking and dock padding. And it's not a secret that code definitely benefits from dividing it into smaller units.

If you know that your user control will not be used as the base for another control (you could make it sealed as a reminder), you can again use anchoring. Here's the funny part: You can use anchoring inside a control that lives on a form that in turn is used as the base for another form. But of course, you cannot use anchoring to position the user control on the base form.

Conclusion

  • Don't use anchoring on forms or (user) controls you want to use as the base for other forms/controls
  • Look at what you can achieve with panels, docking and dock padding
  • Reduce complexity by using user controls (you may want to read a bit about user controls here).

Update (28.10.2003)

Since the original post, I have written two follow-ups that may be of interest for developers using VI and/or docking:

12 Comments

Comments have been disabled for this content.