Click here to Skip to main content
Click here to Skip to main content

Divider Panel - A tutorial on creating a custom Windows Forms control from Start to Toolbox

, 13 Sep 2004
Rate this:
Please Sign up or sign in to vote.
A tutorial on building a custom C# Windows Forms control, covering topics such as inheritance, attributes, overrides, documentation, designers, signing and VS.Net ToolBox integration.

Divider Panel Tuorial Part I- Creating a custom Windows Forms control from Start to Toolbox

Contents

Introduction

In this tutorial we will walk through the process of creating a custom Windows Forms control from the start of the project all the way to inclusion in the Visual Studio ToolBox. The control will be a simple DividerPanel - an inherited Panel control that features selectable border appearance and border sides. After completing this tutorial, readers should have a basic foundation on class inheritance, creating custom properties, overriding base methods, using property comments, creating a ToolBox icon, creating a simple designer class and integrating a custom control into the Visual Studio ToolBox. Along the way we will discuss some best practices and detail some shortcuts in Visual Studio that help to simplify control development.

Creating a New Solution

Creating a new Blank Solution

When creating a new project in Visual Studio for control development, it's usually a good idea to start with a new Blank Solution rather than jumping straight into a new Control Library project with the project wizard. In doing so, you can create multiple projects within the one solution - this allows your test application and control library to remain as separate projects, and also adds the ability to easily share linked classes and include global solution items.

To create a new Blank Solution, select File > New > Blank Solution. In the New Solution dialog, enter the solution name as Windows Forms Divider Panel and click OK.


Adding a new project

Once your new solution has been created, right-click on the solution title and select Add > New Project. When the Add New Project dialog opens, select the Windows Control Library option, and enter DividerPanel as the project name.

The wizard will create the control library with two files by default: UserControl1.cs and AssemblyInfo.cs. For this tutorial we will delete UserControl1.cs and create our control in a new, empty class.

Highlight UserControl1.cs then right-click and select Delete to remove it from the project.

Adding a new class

Next, right-click on the DividerPanel project in Solution Explorer, then select Add > Add Class from the context menu. In the Add Class dialog, enter DividerPanel.cs as the class name and click OK.


Inheriting From Existing Controls

Inheritance is one of the major factors that makes object oriented programming so powerful. When we inherit from an existing class we automatically pick up all of the base classes' functionality and gain the ability to extend upon it to create a more specialized class. All Windows Forms controls at some point must inherit from System.Windows.Forms.Control, as it encapsulates all of the basic properties and methods the framework needs to host a class as a control on a Form.

Fortunately inheriting from an existing control is a snap - once you have decided the control that has the base functionality you wish to extend upon, it takes just one addition to your class declaration line to make your class inherit from it:

public class DividerPanel : System.Windows.Forms.Panel
{
}

For our DividerPanel control, we have specified that we are inheriting our base functionality from the standard System.Windows.Forms.Panel control. In doing this, our new control now has all of the Properties and Methods of the Panel control - we can now add our own custom properties to it, and override some of the Panel controls methods in order to implement our customizations.

Adding Properties and Accessors

In order to implement our DividerPanel, we are going to add two new properties: BorderSide and Border3DStyle. For the BorderSide property we will be using a reference to a value from the System.Windows.Forms.Border3DSide enumeration, and for the Border3DStyle property we will be using a reference to a value from the System.Windows.Forms.Border3DStyle enumeration.

While there a few different ways we can expose these properties, there is only one good way - create a private variable for use by methods within our control class, and a complementary public accessor for exposing the property to other classes that will use the control, like so:

// This system of private properties with public accessors is a
// best practice coding style.
// Note that our private properties are in camelCasing -
// the first letter is lower case, and additional word 
// boundaries are capitalized.

private System.Windows.Forms.Border3DSide borderSide;
private System.Windows.Forms.Border3DStyle border3DStyle;

// Next we have our public accessors, Note that for public accessors
// we use PascalCasing - the first letter is capitalized and additional
// word boundaries are also capitalized.

public System.Windows.Forms.Border3DSide BorderSide
{
    get { return this.borderSide; }
    set
    {
        if( this.borderSide != value )
        {
            this.borderSide = value;
            this.Invalidate();
        }
    }
}

public System.Windows.Forms.Border3DStyle Border3DStyle
{
    get { return this.border3DStyle; }
    set
    {
        if( this.border3DStyle != value )
        {
            this.border3DStyle = value;
            this.Invalidate();
        }
    }
}

In the above code we first define two private properties: borderSide and border3DStyle - these are the variables we will use within our class. As these are both defined with the private attribute they cannot be accessed by any code outside of our control class. You will also note that I have specified the full path to the property objects -

// good
private System.Windows.Forms.Border3DSide borderSide;

As I have included a using System.Windows.Forms directive for my class I could have just declared the property as:

// bad
private Border3DSide borderSide;

But it is always good practice to include the full path to alleviate possible naming obscurities.

Next we have two public properties, both using get and set accessors. In both cases the get accessor simply returns the value of our private property, while the set accessor first checks if the value being set differs from current, and only if so it updates our private property and calls the Invalidate() method to force our control to repaint.

We could have simply created two public only properties as in the sample below, but then we could not do any processing such as the Invalidate() call on set, and there would be no way for our class to know when a value had been changed.

// This is bad coding, never expose public properties like this!
// Always use private variables with complementary public accessors

public System.Windows.Forms.Border3DSide BorderSide;
public System.Windows.Forms.Border3DStyle Border3DStyle;

Even if you don't intend on doing any processing in you public accessors, you should still always stick to good quality coding conventions such as this. The next point is my choice of naming conventions - many people still use naming conventions from other languages such as m_BorderSide instead of my choice of borderSide. My personal choice is based on 3 different sources: first is the names that Visual Studio will automatically assign to controls that are dragged and dropped from the ToolBox, and second is that fact that the framework itself uses this naming convention for most of its private properties (a quick look at any of the framework classes with a decompiler or reflector will confirm this fact). If Microsoft decided it was the best practice naming convention for the .Net framework itself, it's natural to assume it's the best practice when coding for the .Net framework.

The third reason for not using prefixes such as m_ to denote member variables is that you cannot take full advantge of Visual Studio's Intellisense features if you do so. Which brings me to another point of note in the accessor code, my usage of the this prefix - technically there is no need and no advantage in using the this directive in the above accessor code. However, by using the this directive you can take advantage of Intellisense's auto word completion system to fill in the rest of your variable name, saving coding time and saving possible problems with typos.

Any variables you define should always be explicitly assigned a value before they are used. Always set initial values in the class's constructor, or in a method called fom your constructor. Variables without initial value assignments can sometimes create problems that can take hours to find at a later time. For our DividerPanel class, the Constructor looks like this:

// This is the Constructor for our class. Any private variables 
// we have defined should have their initial values set here. It 
// is good practice to always initialize every variable to a specific 
// value, rather then leaving it as an inferred value. For example, 
// all bool's should be set to false rather than leaving them as an 
// inferred false, and any objects referenced that are initially null 
// should be explicitly set to null. (eg. myObject = null)

public DividerPanel()
{
    // Set default values for our control's properties
    this.borderSide = System.Windows.Forms.Border3DSide.All;
    this.border3DStyle = System.Windows.Forms.Border3DStyle.Etched;
}
        

The constructor is the first method called in any class, and is called upon instantiation. You would normally carry out any initialization work necessary here, to ensure that everything is ready to go before any other methods can be called by code using your class. Constructors are always named the same as their parent class, and every class that is to be used as a control must have a parameter-less public constructor if it is to be used with the Visual Studio designer, or be visible to COM clients.

Overriding Inherited Methods

When you override a method from a base class, the CLR will run your code instead of the code normally contained in the base class's corresponding method. This allows you to easily change the behaviour and extend upon the functionality of most of the base controls in the framework. In order to add a border to our DividerPanel, we are going to override the OnPaint method in the base Panel control. Once we have overidden a base class's method, we can still call the base functionality by using the base keyword.

Viewing the inheritance path using the Class View

Naturally every method has it's own unique parameters and in order to perform an override we need to know exactly what those parameters are. Fortunately Visual Studio makes it a snap for us to add overrides when creating custom controls. On the right side of the Visual Studio window you have the Solution Explorer displayed by default, at the bottom of the Solution Explorer panel, click the tab labelled Class View . Next expand out the DividerPanel class and you will see something the same as pictured above.

The Class View is also handy for understanding the path of inheritance your control uses. In our case we can see that our inheritance path is: DividerPanel < Panel < ScrollableControl < Control < Component < MarshallByRefObject < Object.

Using the Class View to simplify overriding methods

For our DividerPanel control, we need to override the OnPaint method, which as you will see is inherited all the way down the tree from the Control class. Locate the OnPaint method, right-click it and select Add > Override.

Visual Studio will then insert an overridden version of the OnPaint method into our class, and we can then add the code that will give our control it's extended functionality

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    // allow normal control painting to occur first
    base.OnPaint(e);

    // add our custom border
    System.Windows.Forms.ControlPaint.DrawBorder3D (
        e.Graphics,
        this.ClientRectangle,
        this.border3DStyle,
        this.borderSide );
}

The first line we'll add to the overriden method is base.OnPaint(e); - this line executes the overridden code from the Panel control, passing the PaintEventArgs value. If we excluded this base.OnPaint(e); method call, we would have to take care of painting everything that our DividerPanel needs ourselves, which of course means we would have to write code that handles all of the painting required for a Panel control.

After the base control has been painted (and only after) we use the System.Windows.Forms.ControlPaint.DrawBorder3D method to paint our custom border(s) over the top of the Panel control, using the values from the custom properties we added earlier. As a side note, the System.Windows.Forms.ControlPaint class is worth your time to investigate as it contains a bevy of static methods that simplify the painting of many Windows type controls such as Buttons, CheckBoxes, RadioButtons, Grids etc.

At this stage we now have a fully functional DividerPanel control, that could be compiled and manually referenced in an application, but don't stop now because we are far from finished!

Adding Property Descriptions & Documentation Support

In order to make our control look and feel like one from the framework ToolBox, we need to add descriptions to our new public accessors, and create a new properties group. Without this, our new properties will be listed as Misc, and it will not be clear exactly what our new properties are intended to do to other developers. Once again, adding property descriptions is a snap, and while we're at it well include support for creating Xml documentation files which can later be created with utilities such as NDoc.

Adding property descriptions

To achieve these, we'll modify our public accessors using the code sample below. The <summary> lines are used to generate Xml documentation files by Visual Studio - to add a summary to any property or class, simply position the caret to a line directly above your property/class and enter three forward slashes (///). Visual Studio will automatically build the required summary structure and all you need to do is fill in the blanks.

The next addition we make here is the designer attributes line, starting with the Bindable attribute. We'll step through the attributes added here one by one:

Bindable(true) - When set to true, any changes to this property will raise a property changed notification, which makes your designer class (discussed later) aware of changes made at design time.

Category("Border Options") - The Category attribute specifies where this property should be grouped in the Properties panel of the forms designer. If this attribute is not set, your new property will be listed under "Misc".

DefaultValue(System.Windows.Forms.Border3dSide.All) - this attribute tells the Visual Studio designer what the default value of the property is, and is used to highlight changes from the default value with bold text. In our screenshot you can see that the value "Etched" is not in bold because we are using the default value, but the value "Top" is in bold because we have changed it from the default value of "All". Note that this attribute has nothing to do with the default value assigned to the property - you must still set a default value for all properties in your controls' constructor.

Description("Specifies the sides of the panel to apply a three-dimensional border to.") - the Description value is displayed at the bottom of the properties panel whenever a property is selected, and should clearly convey to developers what the property does.

/// <summary>
/// Specifies the sides of the panel to apply a three-dimensional border to.
/// </summary>
[Bindable(true), Category("Border Options"), 
DefaultValue(System.Windows.Forms.Border3DSide.All),
Description("Specifies the sides of the panel to apply a 
three-dimensional border to.")]
public System.Windows.Forms.Border3DSide BorderSide
{
    get { return this.borderSide; }
    set
    {
        if( this.borderSide != value )
        {
            this.borderSide = value;
            this.Invalidate();
        }
    }
}

/// <summary>
/// Specifies the style of the three-dimensional border.
/// </summary>
[Bindable(true), Category("Border Options"), 
DefaultValue(System.Windows.Forms.Border3DStyle.Etched),
Description("Specifies the style of the three-dimensional border.")]
public System.Windows.Forms.Border3DStyle Border3DStyle
{
    get { return this.border3DStyle; }
    set
    {
        if( this.border3DStyle != value )
        {
            this.border3DStyle = value;
            this.Invalidate();
        }
    }
}

Adding Toolbox Support

Adding Toolbox support to our new control is just a simple, and involves creating a bitmap to be used as a Toolbox icon for our control, and setting a couple of attributes so that Visual Studio knows how to display our control in the Toolbox.

Adding a Toolbox icon

To create an icon for our control, right-click on our DividerPanel project entry in Solution Explorer, then click Add > New Item. In the dialog that opens, select Bitmap File and set the name to DividerPanel.bmp.

Now highlight DividerPanel.bmp in Solution Explorer and set the Build Action to Embedded Resource.

Next Open the DividerPanel.bmp for editing, and set both the Height and Width to 16, and set the Colors property to 16.

Now paint your icon bitmap and save it:

Painting a Toolbox icon

The final step is to add two new attributes to our DividerPanel class, so that the compiler knows to associate the bitmap and to allow the control to be included in the Visual Studio Toolbox:


[ToolboxItem(true)]
[ToolboxBitmap(typeof(DividerPanel))]
public class DividerPanel : System.Windows.Forms.Panel
{
}

The first attribute we are adding allows our class to be used as a Toolbox item - if you omit this attribute Visual Studio will imply it is already set to true, but as always it is good coding practice to explicitly set this attribute so that your original intentions are always clear when revisting your code. The second line tells the compiler to associate the DividerPanel.bmp file with our control. The name specified in this attribute is simply the resource name of the bitmap, excluding the .bmp extension. As a final note, Toolbox icons must always be bitmap files, with a maximum color depth of 16 colors, and dimensions of 16x16.

Adding a Simple Designer Class

We could now compile our control, add it to the Toolbox and start dragging onto Forms at will, but there's one potential problem we need to address first:

The Panel control we derived from has a BorderStyle property which can be set to None, FixedSingle or Fixed3D - as we are going to be painting our custom border over the top of the standard Panel control, we should remove the BorderStyle property so that there's no confusion about which property developers should use, and also to avoid ugly visual artifacts from someone setting styles for both sets of properties.

There are a few ways to achieve the result we want, but the cleanest way is to create a simple designer class that filters the property list, removing BorderStyle from the properties window altogether. Doing it this way leaves the BorderStyle property intact so that developers can still set it programmatically in their code should they wish to do so.

To start our designer class, right-click on the DividerPanel project in Solution Explorer, then select Add > Add Class from the context menu. In the Add Class dialog, enter DividerPanelDesigner.cs as the class name and click OK.

Adding a reference to System.Design.dll

To implement our designer class, our project needs to reference System.Design.dll from the framework. To add the reference, right-click on References in Solution Explorer, then click Add Reference. In the Add Reference dialog, select System.Design.dll as pictured above, and click OK.

Open DividerPanelDesigner.cs, and change the Class line so that we are inheriting from the System.Windows.Forms.Design.ScrollableControlDesigner class:

public class DividerPanelDesigner : 
   System.Windows.Forms.Design.ScrollableControlDesigner
{
}

Overriding the PreFilterProperties method

Now change to Class View and expand our DividerPanelDesigner class until you get down to the ControlDesigner class, the inheritance path being: DividerPanelDesigner < ScrollableControlDesigner < ParentControlDesigner < ControlDesigner. Now locate the PreFilterProperties method, right-click it and select Add > Override.

As before, Visual Studio will automatically add the overridden method into our designer class code. All we have to do now is add the code to filter out our unwanted BorderStyle property:

protected override void PreFilterProperties(
   System.Collections.IDictionary properties)
{
    properties.Remove("BorderStyle");
}

And finally add a designer attribute to our DividerPanel class to link our designer class like so:

[ToolboxItem(true)]
[ToolboxBitmap(typeof(DividerPanel))]
[DesignerAttribute(typeof(DividerPanelDesigner))]
public class DividerPanel : System.Windows.Forms.Panel
{
}

For this tutorial that's all our designer class needs to do, so we'll leave it at that for now. Creating designer classes is a good subject for a book, not a simple tutorial such as this, but at least you now have an introduction on how to create and use them.

Assembly Attributes & Signing

The final steps we should do before releasing our new control on the unsuspecting public are related to the compilation process. The additions discussed here are all non-essential but do represent best practice coding and a very small amount of work, so there's no reason to omit them.

First we'll open up the AssemblyInfo.cs file and provide some values to the default assembly attributes as follows:

[assembly: AssemblyTitle("Divider Panel")]
[assembly: AssemblyDescription(
  "A Panel control with selectable border appearance")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("CodeShack")]
[assembly: AssemblyProduct("Divider Panel Tutorial")]
[assembly: AssemblyCopyright("Copyright (c) 2003-2004 CodeShack")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

Next we'll add a couple more assembly attributes that are not included by default (I'll let my code comments speak for themselves):

// Flagging your assembly with the CLS Compliant attribute
// lets the Framework know that it will work with all
// CLS Compliant languages, and theoretically with all 
// future framework platforms (such as Mono on Linux).
// If possible you should avoid using any non-CLS compliant code 
// in your controls such as unsigned integers (uint) and
// calls to non-framework assemblies.
[assembly: System.CLSCompliant(true)] 

// The ComVisible attribute should always be explicitly set for controls.
// Note that in order for your control to be consumed by
// COM callers, you must use parameter-less constructors
// and you cannot use static methods.
[assembly: System.Runtime.InteropServices.ComVisible(true)]

And now the final step before we can use our new control, signing your assembly. Signing your assembly is always good practice as it prevents modified or corrupted libraries from being loaded, protecting the application using it against things such as download errors or tampering.

Using the Strong Name utiltiy to create a new key pair

First we must use the Strong Name utility provided with Visual Studio to create a new public/private key pair for our assembly. To do this open up the Visual Studio Command Prompt by clicking Start > All Programs > Visual Studio.Net > Visual Studio .Net Tools > Visual Studio Command Prompt.

At the command prompt enter:

sn -k [outputfile].snk

If you didn't specify the output file to be in your project directory, copy it there now and then modify the AssemblyInfo.cs file as follows, so that Visual Studio knows the relative path to your key file:


[assembly: AssemblyKeyFile("..\\..\\..\\DividerPanel.snk")]

Using the Control

Now all that's left is to build our control, and add it to the Toolbox so we can use it in our applications. Once you are satisfied that your control is bug-free and feature complete, change the Build Configuration in Visual Studio to Release and hit F5 to to compile it.

Adding a custom control to the Toolbox

Next create a new Windows Application project and open a form in design view so that the Windows Forms Toolbox is visible. Right-click on the Toolbox and select Customize Toolbox, when the Customize Toolbox dialog opens, click the .Net Framework Components tab, click Browse, locate the DividerPanel.dll file in the bin\Release directory and click OK.

You will now see the DividerPanel control in the Toolbox items list, and you are ready to start dragging it into your applications.

Your new control in the Toolox, and ready to use

Summary

In this tutorial we have touched on the following topics:

  • Creating a blank solution as a better starting point for a control project
  • Inheriting functionality from existing controls
  • Adding new Properties and Accessors to a control class
  • Property naming conventions
  • Overriding inherited methods
  • Adding property descriptions and documentation support
  • Adding Visual Studio Toolbox support
  • Simple designer class creation and usage
  • Specifying assembly attributes and signing a library using the strong name tool
  • Adding a custom control to the Visual Studio Toolbox

Release History

  • Version 1.0: 29 August 2003 - Initial posting
  • Version 1.01: 12 September 2004 - Edited some terminology, added additional tips.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Furty
Web Developer
Thailand Thailand
Furty will code for food.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberRenju Vinod12-Dec-12 20:45 
GeneralOne of the Best Tutorials I Have Seen Pinmemberm904begley20-Oct-10 9:00 
GeneralMy vote of 4 Pinmemberidenty30-Jun-10 2:11 
QuestionAdding override in MVS2008? Pinmembersomeonecool5-Nov-09 5:59 
Guys, how can i add an override using GUI in MVS2008? There is no Add->Override menu item in this version of Visual studio.
GeneralGreat Article PinmemberAnh Nguyen Ngoc4-Jun-08 21:26 
GeneralVS 2005 in Vista PinmemberEric Brand9-Nov-07 7:39 
QuestionError : there are no components in ... PinmemberPrem Malhotra5-Oct-07 20:42 
QuestionRe: Error : there are no components in ... PinmemberPrem Malhotra5-Oct-07 20:51 
GeneralGreat Introduction Pinmembermelchizedek5-Jul-07 6:47 
QuestionAdding properties with sub properties & removing propeties for user controls Pinmemberabyclassic12-Jun-07 0:55 
GeneralError including in toolbox [modified] PinmemberJohnny Zhivago4-Jun-07 11:59 
QuestionHow can I create output file (snk) when creating a custom Windows Forms control from Start to Toolbox Pinmemberheroz10012-May-07 13:33 
AnswerRe: How can I create output file (snk) when creating a custom Windows Forms control from Start to Toolbox PinmemberFurty12-May-07 15:12 
QuestionDeploying & Shipping window applications using custom Windows Forms Controls Pinmemberabyclassic9-May-07 1:20 
AnswerRe: Deploying & Shipping window applications using custom Windows Forms Controls PinmemberFurty9-May-07 1:39 
GeneralRe: Deploying & Shipping window applications using custom Windows Forms Controls Pinmemberabyclassic12-Jun-07 0:57 
GeneralRe: Deploying & Shipping window applications using custom Windows Forms Controls PinmemberFurty12-Jun-07 1:08 
GeneralAmazingly readable! PinmemberRavi Bhavnani19-Dec-06 8:20 
GeneralSystem.Windows.Forms.GroupBox PinmemberStasius4-Dec-06 2:54 
GeneralToolbox Icon not working PinmemberSteve Barnett6-Sep-06 3:30 
GeneralRe: Toolbox Icon not working PinmemberSteve Barnett6-Sep-06 3:41 
GeneralSorry! Pinmemberdl4gbe17-Apr-06 0:40 
GeneralRe: Sorry! PinmemberFurty17-Apr-06 2:02 
GeneralRe: Sorry! Pinmemberigor0223-Aug-06 22:57 
GeneralRe: Sorry! Pinmemberbulebirds19-Nov-06 3:59 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 14 Sep 2004
Article Copyright 2003 by Furty
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid