Contents
Introduction Creating a New Solution Inheriting From Existing Controls Adding Properties and Accessors Overriding Inherited Methods Adding Property Descriptions & Documentation Support Adding Toolbox Support Adding a Simple Designer Class Assembly Attributes & Signing Using the Control Summary
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 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. 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. 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 Sys tem.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 FUNCTIONAL IY WISH TO EXTEND UPON, IT TAKES JUST One Addition To 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 INE ONE - CREATE A Private Variable for Use by Methods WITHIN OUR Control Class, And A Complementary Public Accessor for Exposing T 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 can not 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 since 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 Border;
public System.Windows.Forms.Border3DStyle Border3DStyle; Even if you do not 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 AssumeTe's The Best Practice When Coding for the .NET Framework. The Third Reason for Not Using Prefixes Such As M_ T o denote member variables is that you can not 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 shop be set to false rather Than Leaving the as an
// Inferred False, and any Objects Reference That Are InIntially 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 base functionality by using the base keyword. 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 inheritance your control buy. In Our Case We can see Our inheritance path is: DividerPanel ScrollableControl // 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 COM piled and manually referenced in an application, but do not 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. To Achieve these, WE ' ll modify our public accessors using the code sample below The ) -. 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> [Bindable (True), Category ("Border Options"), DefaultValue (System.windows.Forms.Border3dside.all), Description ("Specifier 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 (); } } } /// /// specifies the style of the three-dimensional border. /// summary> [Bindable (True), Category ("Border Options"), DefaultValue (System.Windows.Forms.Border3DStyle.etched), Description ("Specifier 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. 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: 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. 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.SCROLLAMS.DESIGN.ScrollableControlDesigner Class: Public Class DividerPanelDesigner: System.windows.Forms.Design.ScrollableControlDesigner { } Now change to Class View and expand our DividerPanelDesigner class until you get down to the ControlDesigner class, the inheritance path being:. DividerPanelDesigner System.collections.idictionary property { Properties.Remove ("BorderStyle"); } And finally add a designer attribute to our dividerpanel class to link our design [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")] [assmbly: 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 knowledge 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 assembly. [assmbly: 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 Construction // 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. 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 did not 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. 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. 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 na ming 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