Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / Windows Forms
Article

Designing Nested Controls

Rate me:
Please Sign up or sign in to vote.
4.94/5 (35 votes)
2 Jul 2009CPOL6 min read 130.5K   4.1K   81   45
An article on enabling nested controls to accept child controls at design time.

NestedControlDesigner Screen Shot

Introduction

This article demonstrates how to allow a Control, which is a child of another Control to accept having controls dropped onto it at design time. It is not a large article, there is not much by way of code, and this may not be either the 'official' or best way to do this. It does, however, work, and is stable as far as I have been able to test it.

Background

I have been using C#, mostly on a hobbyist basis, since VS2003. During that time, I have on occasion tried to design controls, of various types, that contained other controls. When these controls were placed onto a Form or Panel, or whatever, it was never possible to drop a TextBox, for example, onto the inner control and have it be added as a child of that control. I searched for a solution to this problem, but never found one. Recently, I was attempting to help somebody in the C# forum when, more by luck than judgment, I found a methodology which I think solves the problem. After I finished helping the other CPian, I knocked up a very quick demonstration and wrote this article. I wrote it partly because I was pleased to have finally found a resolution to the problem, but mostly because I had not seen an example of this technique anywhere and thought it important to get it out there.

Using the Code

The demo application that goes with this article consists of the Application Main Form, and two Control Libraries. The first of these NestedControlDesignerLibrary, contains the code for TestControl and TestControlDesigner.

TestControl consists of a UserControl which in turn contains two Panel controls. One docked to the top contains a Label to act as a caption for the whole control, and the second, which is the one that I have elected to enable to accept child controls, fills the remainder of the space.

Here is the code for TestControl.

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace NestedControlDesignerLibrary
{
    /// <summary>
    /// A test Control to demonstrate allowing nested Controls
    /// to accept child controls at design time.
    /// </summary>
    [
    Designer(typeof(NestedControlDesignerLibrary.Designers.TestControlDesigner))
    ]
    public partial class TestControl : UserControl
    {
        public TestControl()
        {
            InitializeComponent();
        }

        #region TestControl PROPERTIES ..........................
        /// <summary>
        /// Surface the Caption to allow the user to 
        /// change it
        /// </summary>
        [
        Category("Appearance"),
        DefaultValue(typeof(String), "Test Control")
        ]
        public string Caption
        {
            get
            {
                return this.lblCaption.Text;
            }

            set
            {
                this.lblCaption.Text = value;
            }
        }

        [
        Category("Appearance"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)
        ]
        /// <summary>
        /// This property is essential to allowing the designer to work and
        /// the DesignerSerializationVisibility Attribute (above) is essential
        /// in allowing serialization to take place at design time.
        /// </summary>
        public Panel WorkingArea
        {
            get
            {
                return this.pnlWorkingArea;
            }
        }
        #endregion
    }
}

There isn't much to it, but then, it doesn't actually do very much; as a control, it's pretty useless. Its only purpose is as a demonstration of this methodology.

There are two important parts:

  • The Designer Attribute just above the class statement, which tells the VS Forms Designer that this control has its own designer and where to find it.
  • The WorkingArea property and its associated attributes. This property is used in the Designer to set the Control Hosting ability. The Attribute ensures that when a control is dropped onto the WorkingArea at design time, it gets serialized. As part of the serialization process, the Designer ensures that the dropped control is automagically added to the Controls collection of WorkingArea, not the Controls collection of the main control.

The Designer code:

C#
namespace NestedControlDesignerLibrary.Designers
{
    public class TestControlDesigner : ParentControlDesigner
    {
        public override void Initialize(System.ComponentModel.IComponent component)
        {
            base.Initialize(component);

            if (this.Control is TestControl)
            {
                this.EnableDesignMode((
                   (TestControl)this.Control).WorkingArea, "WorkingArea");
            }
        }
    }
}

That's it. I find it so frustrating that a problem that has bugged me for years can be solved with so little code. I am in no way an expert on Designers, but even I can understand this. The TestControlDesigner inherits from ParentControlDesigner, which according to MSDN is the:

Base designer class for extending the design mode behavior of a Control that supports nested controls.

As we are dealing with nested controls here, this seemed appropriate. When the designer initializes, it calls Initialize on its base class, to ensure default behaviours take place, and then it calls EnableDesignMode with the control nominated to receive the dropped controls at design time, WorkingArea, in this case. The host control needs to be cast to the correct type of control since designers, in general, store this information as a reference to a Control and the name used to expose the control to the user as a string. The call to EnableDesignMode can be repeated for each child control that needs design time capability. For more information about this, see EnableDesignMode on MSDN.

The code and the control presented above has a significant drawback The WorkArea behaves like panel1 and panel2 of a SplitContainer in the Properties window. That means that if you expand it, you get to see all of its public properties. This includes the Dock property, which for this control makes no sense, since WorkArea being docked is a fundamental part of the design, and to allow the user to alter that is just plain daft.

The more experienced of you will know how to overcome this problem, but for the newer coders, I have created an ImprovedTestControl in the ImprovedNestedControlDesignLibrary. The only difference from the vanilla control is that in the improved version, WorkArea is a User Control that inherits from Panel, rather than a simple Panel. The reason that I chose to do this is that it enabled me to give it its own Designer, which is the technique that I elected to use to hide the unwanted properties. There are loads of other ways of doing this, and several excellent articles on how to do it can be found here on CodeProject. Enter 'hiding inherited properties' into the search box on the Home page and hit Enter. The first article I found was Hiding Inherited Properties from the PropertyGrid by Shaun Wilde, deals specifically with this problem; the others do so to a greater or lesser extent. Have a root round and find a method that you like for use in your own code.

Anyway, here is the code for the new WorkArea:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ImprovedNestedControlDesignerLibrary
{
    [
    Designer(typeof(ImprovedNestedControlDesignerLibrary.Designers.WorkingAreaDesigner))
    ]
    public partial class WorkingAreaControl : Panel
    {
        public WorkingAreaControl()
        {
            InitializeComponent();
            base.Dock = DockStyle.Fill;
        }
    }
}

As stated before, this control descends from Panel.

Because of the way I have hidden the Dock property, it will not be available when designing the ImprovedTestControl, so it is necessary to set it appropriately in the constructor. That's all there is, except for the Designer.

Here's the code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms.Design;

namespace ImprovedNestedControlDesignerLibrary.Designers
{
    public class WorkingAreaDesigner : ScrollableControlDesigner
    {
        protected override void PreFilterProperties(
                  System.Collections.IDictionary properties)
        {
            properties.Remove("Dock");

            base.PreFilterProperties(properties);
        }
    }
}

All that this designer does is to override the PreFilterProperties method. This method has as its only parameter a Dictionary of all of the public properties of its control. Therefore, all that is required is to remove any property that you don't want to show up in the Properties window and pass on the, now slimmer, Dictionary to the base method. Job done!

The demo application is a plain form with one TestControl and one ImprovedTestControl.

Once the TestControl was added, I put a ComboBox on it and gave it some items. For the ImprovedTestControl, I added a Label and a ListBox, again with some items.

Points of Interest

I learned several interesting things whilst writing this article. Not least, I learned that helping others can help you. I also had confirmed to me how poor the Microsoft documentation is for Designer topics.

Here are some links to articles on the Windows Forms design process:

I do hope that some of you find this useful as I was ridiculously pleased to have found a solution to this problem.

History

  • Version 1: 01-Jul-2009.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired
United Kingdom United Kingdom
Retired Systems Admin, Programmer, Dogsbody.
Mainly on Systems for Local Government, Health Authorities,
Insurance Industry - (COBOL eeeeeeeugh).
Inventor of Synchronized Shopping.

Comments and Discussions

 
QuestionThank you Pin
Abdlrhman Shehata22-Dec-15 4:26
Abdlrhman Shehata22-Dec-15 4:26 
QuestionTabControl? Pin
User 1180552716-Dec-15 7:24
User 1180552716-Dec-15 7:24 
AnswerRe: TabControl? Pin
BillWoodruff18-Dec-15 19:15
professionalBillWoodruff18-Dec-15 19:15 
SuggestionNested Controls Pin
TobyTwerl16-Aug-14 11:27
TobyTwerl16-Aug-14 11:27 
QuestionVote of 5 Pin
Smurf IV (Simon Coghlan)24-Mar-14 6:49
Smurf IV (Simon Coghlan)24-Mar-14 6:49 
QuestionDocument Outline? Pin
o_theophilus23-Oct-13 10:58
o_theophilus23-Oct-13 10:58 
QuestionDefaultValue of TestControl Pin
Tomb011-Aug-13 4:54
Tomb011-Aug-13 4:54 
GeneralMy vote of 5 Pin
Member 91500798-Feb-13 16:50
Member 91500798-Feb-13 16:50 
Questionsimpler in VS2010 Pro, .NET 4, via use of IDesigner only, or by use of 'ControlContainer' ? Pin
BillWoodruff16-Jul-11 14:30
professionalBillWoodruff16-Jul-11 14:30 
AnswerRe: simpler in VS2010 Pro, .NET 4, via use of IDesigner only, or by use of 'ControlContainer' ? Pin
Henry Minute17-Jul-11 1:11
Henry Minute17-Jul-11 1:11 
GeneralMy vote of 5 Pin
SveinMD15-Jul-11 14:11
SveinMD15-Jul-11 14:11 
GeneralMy vote of 5 Pin
Groulien8-Mar-11 8:27
Groulien8-Mar-11 8:27 
QuestionAnother problem Pin
Sander Rossel17-Dec-10 12:41
professionalSander Rossel17-Dec-10 12:41 
AnswerRe: Another problem Pin
Henry Minute17-Dec-10 13:20
Henry Minute17-Dec-10 13:20 
GeneralRe: Another problem Pin
Sander Rossel18-Dec-10 0:36
professionalSander Rossel18-Dec-10 0:36 
GeneralRe: Another problem Pin
Henry Minute18-Dec-10 0:43
Henry Minute18-Dec-10 0:43 
GeneralRe: Another problem Pin
Sander Rossel18-Dec-10 2:54
professionalSander Rossel18-Dec-10 2:54 
GeneralRe: Another problem Pin
Henry Minute18-Dec-10 3:03
Henry Minute18-Dec-10 3:03 
GeneralRe: Another problem Pin
Sander Rossel18-Dec-10 3:38
professionalSander Rossel18-Dec-10 3:38 
QuestionIt's an upside down world... Pin
Sander Rossel14-Dec-10 11:20
professionalSander Rossel14-Dec-10 11:20 
AnswerRe: It's an upside down world... Pin
Henry Minute14-Dec-10 11:30
Henry Minute14-Dec-10 11:30 
I think that what is going wrong here is that you will also have to create your own designer for the GroupBoxes.

I strongly suspect that the controls you add to the GroupBoxes at design time are, in reality being added to the Controls collection of the SplitContainer, not to the Controls collection of the GroupBox. You can test this at design time by making sure the GroupBox is not docked and is smaller than the SplitContainer.Panel it sits on. Then add one or two Controls to the GroupBox then drag the GB to a new position, just 10/20 pixels will do. If the Controls move with it, then I am wrong. If they don't move then I am right.
Henry Minute

Do not read medical books! You could die of a misprint. - Mark Twain
Girl: (staring) "Why do you need an icy cucumber?"
“I want to report a fraud. The government is lying to us all.”

GeneralRe: It's an upside down world... Pin
Sander Rossel14-Dec-10 11:44
professionalSander Rossel14-Dec-10 11:44 
GeneralRe: It's an upside down world... Pin
Henry Minute14-Dec-10 11:56
Henry Minute14-Dec-10 11:56 
GeneralRe: It's an upside down world... [modified] Pin
Sander Rossel14-Dec-10 12:07
professionalSander Rossel14-Dec-10 12:07 
GeneralRe: It's an upside down world... Pin
Henry Minute15-Dec-10 5:44
Henry Minute15-Dec-10 5:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.