Wednesday, September 10, 2008

How to create Customized Control in C#?


This is a short and simple demonstration of .NET framework's capability of creating custom controls.

Here I'm going to make a custom control and then, test my control in a Windows application. I have implemented some custom properties for my control, so you can learn how it is done in C#.

Building the Control

  1. Open the Visual Studio and start a new project. Your project must be based on the Windows Control Library template. Call your project ctlCuteButton and click OK.

  2. Once you have your project open, delete the UserControl from the project. Just remove it because the 'User Control' is not exactly what we need here.

  3. Now go to the 'Project' menu: Project->Add User Control... and select the Custom Control template there. 'Custom Control' is what we need in this case. You may call it cuteButton. Now click OK. A new Custom control has been added to your project.

  4. The first thing we must do here is to change the base class of the cuteButton:

    Override the following line:

    public class cuteButton : System.Windows.Forms.Control

    by this one:

    public class cuteButton : System.Windows.Forms.Button

    Now your control is based on the System.Windows.Forms.Button class.

  5. Now let's create some custom properties for our control. This is done by inserting the following code inside the cuteButton class:

    private Color m_color1 = Color.LightGreen;  //first color

    private Color m_color2 = Color.DarkBlue; // second color

    private int m_color1Transparent = 64; // transparency degree

    // (applies to the 1st color)

    private int m_color2Transparent = 64; // transparency degree

    // (applies to the 2nd color)

    public Color cuteColor1
    get { return m_color1; }
    set { m_color1 = value; Invalidate(); }

    public Color cuteColor2
    get { return m_color2; }
    set { m_color2 = value; Invalidate(); }

    public int cuteTransparent1
    get { return m_color1Transparent; }
    set { m_color1Transparent = value; Invalidate(); }

    public int cuteTransparent2
    get { return m_color2Transparent; }
    set { m_color2Transparent = value; Invalidate(); }

    The Invalidate() methoid is used to refresh the design view and all controls inside (the tip from Tom Welch).

  6. And the last thing to do before compiling our control is to override the Paint event. So let's do it:

    // Calling the base class OnPaint

    // Create two semi-transparent colors

    Color c1 = Color.FromArgb(m_color1Transparent , m_color1);
    Color c2 = Color.FromArgb(m_color2Transparent , m_color2);
    Brush b = new System.Drawing.Drawing2D.LinearGradientBrush(ClientRectangle,
    c1, c2, 10);
    pe.Graphics.FillRectangle (b, ClientRectangle);
  7. Now you may compile the control by pressing ++.

Here is the complete contents of cuteButton.cs file (just in case…)

Testing the Control

  1. Open a new instance of the VS .NET. Create a new project choosing the Windows Application template.

  2. From a new Windows Forms project, we can add the compiled custom control to the toolbox. I do this by right-clicking the toolbox, selecting Customize Toolbox, and from the .NET Framework Components tab, clicking Browse and locating the Control Library DLL # (in our case, ctlCuteButton\bin\debug\cuteButton.dll). The component cuteButton will then appear in the Toolbox.

    You can play a bit changing it’s properties (cuteColor1, cuteColor2, cuteTransparent1, cuteTransparent2).

That’s all so far about building and using custom controls.

Good Luck.

(Or, do not need to add control from toolbox, add the control project to your project, then the control will appear automatically.)

1 comment:

Dennis said...

A fast and performing gauge

How the code works

The code consists of a C# application and a custom control. The custom control really is the interesting part.
Deriving from Control

We derive from Control as this doesn't give us all these properties we don't actually need like a usercontrol would give us, for example.

public partial class AGauge : Control

Dealing with properties
Hiding, shadowing unwanted properties

Well, there are still properties that show up in the designer that are not necessary. In C#, you can use the new keyword to get rid of them (shadows in VB).

public new Boolean AllowDrop, AutoSize, ForeColor, ImeMode

Overriding useful properties

For properties that you want to use but with a different behaviour, you can use the override keyword (if overrideable) to tell the program to call this overridden property instead of the implementation of the base class, which in our case is the implementation in Control.

public override System.Drawing.Color BackColor..
public override System.Drawing.Font Font..
public override System.Windows.Forms.ImageLayout BackgroundImageLayout..

Custom properties

To be able to further customize the control in the designer, we need to add some properties of our own. E.g.,

System.ComponentModel.Description("The value.")]
public Single Value..

The Browsable attribute tells the designer to show the property in the toolbox or not. The Category attribute tells the designer where to show the property if the categorized view is selected, and the Description attribute adds a description to the property that the designer can show in the toolbox.
Events and Delegates

An event can carry additional information that is sent to the "listening" program, e.g., the form's event handler for this event.
Custom event arguments

We want the event to carry the number of the range the needle is in (if it changes from being in one range to being in another). To add some data to the event, we derive from the standard event args and add a variable which is initialized in the constructor. This will hold the extra information sent along.

public class ValueInRangeChangedEventArgs : EventArgs
public Int32 valueInRange;
public ValueInRangeChangedEventArgs(Int32 valueInRange)
this.valueInRange = valueInRange;

Event delegate

The event handler "listening" for our event needs to be of a type that "understands" our event. With the delegate statement, we define this type.

public delegate void ValueInRangeChangedDelegate(Object sender,
ValueInRangeChangedEventArgs e);

And the event

[Description("This event is raised if the value falls into a defined range.")]
public event ValueInRangeChangedDelegate ValueInRangeChanged;

The event is of the type we defined in the delegate statement. The Description attribute enables the designer to show a description for the event in the Toolbox.

The constructor is called when the control is created, e.g., before it will be shown in the designer. Here, we set the style of the control to enable double buffering. This isn't really necessary since we will do our own double buffering, but it doesn't hurt to do so.

public AGauge()
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Overriding member functions

We need to override some of the member functions.

First, we override OnPaintBackground to ensure that the background is not painted each time the control is refreshed, this uses too much CPU even if double buffering is enabled. One drawback is that we need to handle the drawing of a background image ourselves, but this isn't too much of a problem.

protected override void OnPaintBackground(PaintEventArgs pevent)

If the control is resized, we need to refresh it. So we override OnResize.

protected override void OnResize(EventArgs e)
drawGaugeBackground = true;

The global variable "drawGaugeBackground" is set to true to tell the control to completely redraw itself. Refresh forces the control to redraw, or if you like to call OnPaint, under the hood, a Windows message is sent, but this is a different story.

Finally, we need to override OnPaint to show some output to the user.

This is what our control really does, it shows the output to the user. It doesn't handle user input like a scrollbar would do. A scrollbar would override OnMouseMove, OnMouseDown, OnKeyPressed, and so on. OnPaint is the heart of our control.

protected override void OnPaint(PaintEventArgs pe)

OnPaint, which is called every time the control is redrawn, e.g., if the value of the gauge changed, determines if it should completely redraw itself or simply paint the background part with the performant function DrawImage. If the background hasn't changed, it only needs to draw the needle, thus avoiding costly GDI+ functions to be called every time. The background changes, e.g., if a property like a color has changed, or the control is resized, for example.