Introduction
While researching a small project to develop round 3D buttons, I came across the very excellent Bob Powell site. Here, I learned all about the PathGradientBrush.
After a considerable amount of brain-wringing, I managed to get to grips with the basics of Matrix
and Transform
. Having thought about writing my own TrackBar
control, but discovering that someone else had already done it, I decided to use my new-found knowledge and try to create an LED vu Meter, such as is found in PC and Mac music recording systems. As is often the case, it was easier than I had first thought. It doesn't do anything at all with Digital Signal Processing, so apologies to anyone who was hoping it would, but I think it looks really nice, so it became the subject of my first submission to The Code Project.
The Code
The control inherits from System.Windows.Forms.UserControl
, and overrides the OnPaint
method. In short, we size each LED so that fifteen will fit nicely within the size of the control, build a 3D border round them, and light them with a PathGradientBrush
according to the Volume
property.
There are fifteen LEDs, each of which uses three colors. Color 1 is the Surround Color for the "unlit" state, Color 2 is the Centre Color for the "unlit" state AND the Surround Color for the "lit" state, and Color 3 is the Centre Color for the "lit" state. I set these colors up in the ledColours
array.
Color[] ledColours =
new Color[] {Color.DarkRed, Color.Red, Color.White,
Color.DarkRed, Color.Red, Color.White,
Color.DarkRed, Color.Red, Color.White,
Color.DarkGoldenrod, Color.Orange, Color.White,
Color.DarkGoldenrod, Color.Orange, Color.White,
Color.DarkGoldenrod, Color.Orange, Color.White,
Color.DarkGoldenrod, Color.Orange, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White,
Color.DarkGreen, Color.Green, Color.White};
This may not be the most elegant implementation, but it works, and makes changing the colors simple, if a little tedious. The more observant reader will no doubt notice that the "Lit Centre" is always white, and I could have reduced the size of the array and simply referred to Color.White
in the code, but this way, it makes it possible for someone to change the "lit" centre color, if white doesn't work for their chosen surround colors.
The DrawLeds
method first calculates the size of each LED, depending on the size of the control. The height is calculated so that fifteen LEDs will fit, with a two-pixel gap between each one.
int ledLeft = this.ClientRectangle.Left + 3;
int ledTop = this.ClientRectangle.Top + 3;
int ledWidth = this.ClientRectangle.Width - 6;
int ledHeight = this.ClientRectangle.Height / ledCount -2 ;
Rectangle ledRect = new Rectangle(ledLeft, ledTop, ledWidth, ledHeight);
Next, we create our GraphicsPath
, add our ledRect
rectangle to it, and create a PathGradientBrush
to use with it.
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(ledRect);
PathGradientBrush pgb = new PathGradientBrush(gp);
We then loop through each LED. The loop has both ascending and descending indices, as explained in the code comments. For each LED, we determine if it's lit or not by comparing the value of j
with the value of ledVal
(set by the "Volume
" property). If our LED is less than or equal to the volume, we light it. (We also light it if it is equal to the peakVal
variable, more of which later.) Once we know if we're lighting this LED or not, we select the "Unlit surround" and "Unlit Centre", or "Lit Surround" and "Lit Centre" colors from the ledColours
array. This is achieved by simple calculation based on the value of i
.
if ((j <= ledVal) | (j == peakVal))
{
pgb.CenterColor=ledColours[i*3+2];
pgb.SurroundColors=new Color[]{ledColours[i*3+1]};
}
else
{
pgb.CenterColor=ledColours[i*3+1];
pgb.SurroundColors=new Color[]{ledColours[i*3]};
}
pgb.CenterPoint=new PointF(ledRect.X + ledRect.Width/2,
ledRect.Y + ledRect.Height/2);
Now for the groovy part. We create a Matrix
and use its Translate
method to effectively change the value of ledRect.X
depending on the value of i
. The Transform
property of our Graphics
instance is then set to our Matrix
, mx
, to move the current LED down the screen. I worked out how to do this with help from Mahesh Shand here. We then fill the LED in with our trusty PathGradientBrush
.
Matrix mx = new Matrix();
mx.Translate(0, i * (ledHeight + 2));
g.Transform=mx;
g.FillRectangle(pgb, ledRect);
Once we've drawn all our LEDs, we have to reset the Graphics
instance to its original offset, so we can draw the border in the right place.
Matrix mx1 = new Matrix();
mx1.Translate(0, 0);
g.Transform = mx1;
The best bit, I thought, was implementing a Peak Level Indicator. This means that the LED at the highest level reached stays lit for a certain amount of time. This is achieved as follows.
First of all, this piece of code in the Volume
property determines if the latest value is higher than the previously saved peak value. If so, it saves it in peakVal
. Also, the timer is started.
if (ledVal > peakVal)
{
peakVal = ledVal;
timer1.Enabled = true;
}
The peak value will stay set either until it is exceeded, or until the timer interval elapses, at which point it will be reset to zero, and the LED will go out.
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
peakVal = 0;
this.Invalidate();
}
Using the code
It's pretty simple to use - the sample application shows how. If you include vuMeterLED.dll in your project, you should be able to drag the control onto your form. If you download the source, note that I've only included the vuMeterLED.cs, Form1.cs and AssemblyInfo.cs files. This is because I developed the code using SharpDevelop, and I don't know if the project files would be readable by VS.
That's about it then! Thanks for reading this far, and I hope it's useful or educational to someone.
What's next?
Well, the control could do with a scale, which shouldn't be too difficult to draw. More of a challenge would be developing an analog style meter with a moving needle. I'm thinking about it...