Introduction
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 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.Browsable(true),
System.ComponentModel.Category("AGauge"),
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.
Constructor
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()
{
InitializeComponent();
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;
Refresh();
}
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.
Conclusion
So it really is possible to write fast and performing controls with GDI+ if we use double buffering and blitting (DrawImage).
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!
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi. I find your article really usefull, but I can't seem to use it on my own. I've tried to add it to my Toolbox items but only an item apears and it is disabled. Can you please help me to insert your controls on a new application (step by step)? This is probably a newbie question... I'm using Visual Studio 2008 and Csharp for my applications.
I apreciate your time. Thank you in advance.
Regards. Daniel Coelho
Note1: I forgot to say that I'm using WPF ( Windows Presentation Foundation).
modified on Thursday, November 12, 2009 5:54 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
 | Thanks  candresjimenez | 14:41 10 Mar '09 |
|
|
 |
|
|
 |
|
 |
Hello Mr. Bauer! You wrote a similar control as an ActiveX once, using C++, ATL, and GDI. Can I have the sourcecode of this?
Kind Regards und danke! Dieter
Dieter.Arand@us.bosch.com
...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Sorry no can't do. This was a commercial product (scaleable at runtime... much more work).
Don't Panic, debug it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I want to show half circled gauge graph on my web page, Is any one having the code in c#.net for that.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
 | Web  Fradique Lee | 14:58 11 Nov '08 |
|
|
 |
|
 |
I make a gradient with the defined color and white...
replace the line 1423 by
Rectangle r = new Rectangle(m_Center.X - m_RangeOuterRadius[counter], m_Center.Y - m_RangeOuterRadius[counter], 2 * m_RangeOuterRadius[counter], 2 * m_RangeOuterRadius[counter]); LinearGradientBrush l = new LinearGradientBrush(r, m_RangeColor[counter], Color.White,LinearGradientMode.ForwardDiagonal); ggr.FillPie(l, r, rangeStartAngle, rangeSweepAngle);
Thanks for this splendid tool.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
Hi A.J.!
Excellent article and control. I took the liberty of refactoring your demo app to extract AGuage into its own library, changing the namespace to AJBauer. This is so I can use it "as is" in another CodeProject article (coming soon!) while making it more obvious that AGuage is your code. Will email you the projects if you like [I did not see a way of directly sending them to you via codeproject].
Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi Steven,
Thank you for refering to my project and for putting the code to good use.
Regards A.J.
Don't Panic, debug it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi A.J. !
I've implemented a context menu on your gauge in my project, to copy it to clipboard.
Would you be interested if I send you the functions I created?
Julien
(and congratulations for your great job!)
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi Julien,
I'm sure there is people who would like to have this feature for the gauge. Why not write a small article and put a link here? I would definitely like to take a look at the code, thank you for your work.
A.J.
Don't Panic, debug it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, thank you for making this available for the community. I downloaded it, opened the project and the sample worked fine. However, when I add a new gauge to a form, by default it is displayed with properties as follows : RangeEndValue = 300 Max = 400 from 0 to 300 is colored in green, 300 to 400 in red. When I change RangeEndValue to 350, the green doesn't move to 350. 2nd, when I change max value to 600, RangeEndValue to 600, then I have, first part (0-300) in green, a 2nd part (300-400) in red, a 3rd part agin green. Sothe question, how can we have a gauge with values between 0 and 1000, green from 0 to 750, red from 750 to 1000. Thanks agin for this nice component.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I have just read the thread about "range visible" and understood how we can play with ranges. well done A.J
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Andreas,
great work. Much less work in C# than your old BCGauge in ATL (Audi and Daimler are still using it) - and the result still impressive!
Best wishes!
Martin Hagenow
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi,
This control is very nice and fast !
Would anyone know a way to display ranges' limits in colored areas ?
I'm thinking of adding a method to display text from a radius and a gauge value, but if that have ever been done before, it will be a nice gain of time for me.
Thanks
Julien V.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi there all,
I see that there are questions about the control from time to time. Guys, this code is provided as is, it is supposed to be educational at most.. If I wanted to write a full blown control I would have invested more time and would have tried to sell it too. The focus was to show that you can build performing code with .NET thats all. If you have problems with it dig into the code and try to figure it out yourselves!
Hope you understand and some thanks are always welcome.
Cheers mates and have fun with it. A.J.
Don't Panic, debug it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
A.J.,
Great control, wonderful job. I can see the BaseArcStart property on the 'aGauge1' object is set to the default of 135. However, I am unclear on this. The BaseArcStart looks to the user as if it starts somewhere around 225 and sweeps 270 from there, moving clockwise. Could you provide a frame of reference for the BaseArcStart and BaseArcSweep properties?
Many thanks,
Dr4g0n
-- modified at 1:01 Saturday 6th October, 2007
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Zero = pointing right 90 = pointing down 180 = pointing left 270 = pointing up
positive BaseArcSweep = clockwise
So, BaseArcStart=180,BaseArcSweep=180 would be the upper half of the circle; BasArcStart=190,BaseArcSweep=170 would be a sector in the upper half with both edges pointing upward; etc.
Play with it in design mode, it becomes clear pretty quickly.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|