This code shows you how to build a fast and performing control using C# and .NET 2.0.
I wrote a similar control as an ActiveX once, using C++, ATL, and GDI, and wondered if it is possible to write performing code using .NET and GDI+. I needed it for another project. So I wrote this little control to show that it actually works.
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
public override System.Drawing.Color BackColor..
public override System.Drawing.Font Font..
public override System.Windows.Forms.ImageLayout BackgroundImageLayout..
To be able to further customize the control in the designer, we need to add some properties of our own. For example:
public Single Value..
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;
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,
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.
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
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
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.
So it really is possible to write fast and performing controls with GDI+ if we use double buffering and blitting (
If you like VB better than C#, you can search for "
SpeedyHMI" on SourceForge, this project I wrote contains this gauge written in VB.
Download, build, run and, enjoy!