How can I drop controls within UserControl at Design time?

One of the most frequent questions asked on the community forum is how can I make my userControl act as a containerControl (panel) which allows me to add controls at design time. In V1.0 and V1.1 you had to write a lot if code to do this, but in Whidbey we have added a new concept of NestedContainers which would allow the users to accomplish this using a single method on the ControlDesigners.

Let me explain how you can expose the userControl as "container" at design time.

You may ask why doesnt it work by default?

What you’re seeing is our default encapsulation model for user controls. By default, a user control is treated as a single object when placed on a form. We do this because it may not be your intent to expose the inner layout of your user control (for example, the PropertyGrid control contains several panels, buttons, scroll bars, etc, but it is treated as a single control). So there are cases when you want the userControl to be a single composite control and hence not support it as container at design time. This still remains the default case in whidbey too.

What should I do to make the control act as container at Design time?

This is easy to do. You need to change the designer on your user control:

[Designer(typeof(ParentControlDesigner))]

public class MyUserControl : UserControl {}

Once you do this, the user control can be edited on the form designer’s surface.If you want a region of “content” on your user control to be interactive at design time…

This is the more likely case – you have a control with some child controls on it, but one (or more) of the child controls needs to be a content area where the user can drop additional controls. Within this content area you want the user to be able to select, drag, drop and otherwise manipulate controls, but outside of the area you want the user control to be treated as a single entity. In whidbey, you need to do two things:

1) Add a public read-only property to your user control that exposes the child content control. Write a simple designer that enables the content control to be designed.

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

public Panel MyChildPanel { get { return _myChildPanel; } }

The attribute on the property tells the code generator to write out code for the value contained in this property (normally, read-only properties are skipped).

2) Use EnableDesignMode method to plug in the designer for panel.

[Designer(typeof(MyUserControlDesigner))]

public class MyUserControl : UserControl {

}

class MyUserControlDesigner : ControlDesigner {

      public override void Initialize(IComponent comp) {

            base.Initialize(comp);

            MyUserControl uc = (MyUserControl)comp;

            EnableDesignMode(uc.MyChildPanel, “MyChildPanel”);

      }

}

Here I’ve created a designer (which can…and should be an internal class unless you specifically design for extensibility). In the initialize code for the designer I’ve called EnableDesignMode, passing the panel I want to enable (MyChildPanel), and a text name that will be shown to the user when the panel is selected (“MyChildPanel”). 

Once you do this, the panel will work in the designer. If you click on it, it will be selected and you can set properties on it in the property window. The name for the selected panel will be a mix of the control’s name and the name you provided. For a control named myUserControl1, the panel’s name in the grid will show up as “myUserControl1.MyChildPanel”.

I will discuss the basics of EnableDesignMode in a separate blog entry.