Introduction
There are many 'Analog clocks' at CodeProject and this is yet another (hence the title), but I have designed this one to be slight different. This analog clock is very much customizable (besides being pretty ). You can resize it, change the colors, time, rendering quality add a background image and a lot. Take a look at the list of properties. Much of the inspiration comes from A Vector Graphics Rendered Animated Clock and Public Joe.
Background Mathematics
Let us delve back into some basic mathematics before getting involved in the programming aspects. You all know that a circle is of 360° degrees or 2Π in radians and that there are 12 digits on the face of the clock from one to twelve. To draw the twelve digits on the face, we need to place each of them at same degree from each other, so 360/12 = 30, so we need to place each digit 30° apart from the other. This means starting from 0° , 30°, 60° up to 360°. Also recall that angles are measured positive clockwise and negative anticlockwise. Now how are the minute, hour and second hand drawn. Quite simple, recall that we can calculate any point by using the formulas as seen in the picture below:
We need to know the angle θ and then we can draw a line from the centre of the circle. I.e., from origin to that point for a given θ. θ is the angle or should I say rotation. Note that a second hand takes 60 ticks to complete one complete rotation. So what should be the angle of rotation when sec = 1? Well it should be:
Second Rotation = 2.0 * * sec/60.0
Think of the term sec/60.0 as the percentage of how much rotation must be given for the given value of second. For example:
when sec = 0 , rotation is 0° ( i.e. the second hand is at 12)
when sec = 15 , rotation is 25% ( i.e. the second hand is at 3)
when sec = 30 , rotation is 50% ( i.e. the second hand is at 6)
Now that we have the value of θ or rotation, we can find the value of x and y and draw a line from the origin to the point P using the above mentioned formula.
Minute Rotation = 2.0 * * ( currentmin + currentsec/60.0 )/60.0
Now again think of the term ( currentmin + currentsec/60.0 )/60.0 as the percentage of how much rotation must be given for the given value of currentmin and currentsec. Note that in the above term we have also added second rotation so that the minute hand is updated every second. But updating minute hand every second doesn't look neat as it is updated only as significant changes occur in the value of minute rotation and it would look like that the minute hand moves unevenly. If you wish to update the minute hand after every minute only, just remove the termcurrentsec/60.0. Now when the second hand completes one rotation, the minute hand will move ahead. (In order to see what I am trying to tell, download the source code and observe how the minute hand behaves in the clock that displays the time for Honk Kong and the rest.)
Hour Rotation = 2.0 * * ( currenthour + currentmin/60.0 )/12.0
Similar technique here. The hour hand is updated every minute so we have added the minute rotation too. You can think of how the clock would behave if we didn't update the hour hand every minute.
Now let's move on to the implementation details. All the drawing is done in the function DrawClock: private voidDrawClock(Graphics e)
. The Analog Clock exposes a property named Time
. Whenever the value of this property is changed, the clock is updated.
Enabling Double Buffering
In the constructor of the UserControl, I have enabled Double-buffering on the control which prevents flicker caused by the redrawing of the control. To fully enable double-buffering, we must also set the UserPaint
andAllPaintingInWmPaint
style bits to true
(to read more about Double Buffering read this).
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.UserPaint,true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint,true);
Enabling Anti-aliasing
To make the clock look smooth, I have set the SmoothingMode
and TextRenderingHint
property of the graphics object to Anti-alias. The SmoothingMode
property makes the face of the clock look smooth and theTextRenderingHint
property makes the clock numerals look smooth. Please note that SmoothingMode
has no effect on the numeral that is drawn on the clock.
grfx.SmoothingMode = smoothingMode;
grfx.TextRenderingHint = textRenderingHint;
grfx.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
Here is how the analog clock appears without anti-aliasing.
Drawing the clock face
Next I have made four rectangles inside which circles are drawn. The drop shadow is also an eclipse which is at a slightly offset from the face eclipse. (Please note that there can be other ways to implement this, such as drawing eclipse directly).
//Define rectangles inside which we will draw circles.
Rectangle rect = new Rectangle(0+10 ,0+10 ,(int)x-20 ,(int)y-20);
Rectangle rectinner = new Rectangle(0+40 ,0+40 ,(int)x-80 ,(int)y-80);
Rectangle rectrim = new Rectangle(0+20 ,0+20 ,(int)x-40 ,(int)y-40);
Rectangle rectdropshadow = new Rectangle(0+10 ,0+10 ,(int)x-17 ,(int)y-17);
Next I have drawn a gradient filled eclipse inside all these rectangles,
//Drop Shadow
gb = new LinearGradientBrush(rect, Color.Transparent,
dropShadowColor, LinearGradientMode.BackwardDiagonal);
rectdropshadow.Offset(dropShadowOffset);
if(this.drawDropShadow)
grfx.FillEllipse(gb, rectdropshadow);
//Face
gb = new LinearGradientBrush(rect, rimColor1, rimColor2 , faceGradientMode);
if(this.drawRim)
grfx.FillEllipse(gb, rect);
//Rim
gb = new LinearGradientBrush(rect, faceColor1, faceColor2, rimGradientMode);
grfx.FillEllipse(gb, rectrim);
This gives the basic face of the clock as follows:
Drawing the circular face image
To draw a circular image within the face of the clock, we have to define a circular clipping region and draw image inside it. Once we have done that, we'll have to reset the clipping region. Here is how it is done:
//Define a circular clip region and draw the image inside it.
GraphicsPath path = new GraphicsPath();
path.AddEllipse(rectrim);
grfx.SetClip(path);
if(this.img != null)
grfx.DrawImage(this.img, rect);
path.Dispose();
//Reset clip region
grfx.ResetClip();
where img
represents the image. It is declared as:
private Image img;
The output is something like this:
Drawing the Numerals
Next we need to draw the numerals. But before we do that, we need to move the origin of the control to the center of the client area. (By default it is at the top right corner). We can do so by using the graphic object'sTranslateTransform
method. Here's how it is done.
grfx.TranslateTransform(midx, midy);
I have already defined midx
and midy
as the center of the control. Next we draw the numerals:
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
//Draw Numerals on the Face
int deg = 360/12;
if(drawnumerals)
{
for(int i=1; i <= 12; i++)
{
grfx.DrawString(i.ToString() , textFont ,stringBrush ,
-1 * GetX(i * deg +90) , -1 * GetY(i * deg + 90),format);
As you can see, we have multiplied both the value of x and y by -1 so that the numeral 12 appears on the top, other wise it'll appear on bottom as seen below.
Drawing the Second Hand
First, we will draw the hour hand, then the minute hand and finally the second hand. The order is important because it is the same in real world clocks.
Our second hand can tick in two different ways -- as explained earlier, one is normal tick style which we usually see in quartz clocks and the other is smooth style usually seen in automatic clocks. I have defined enumerations for both of these styles which is used to determine the value of the rotation.
//Draw Minute hand
if(drawMinuteHand)
{
if(minHandTickStyle == TickStyle.Smooth)
minuteAngle = (float)(2.0 * Math.PI* ( min + sec/60.0 )/60.0);
else
minuteAngle = (float)(2.0 * Math.PI* ( min /60.0));
pen.EndCap = LineCap.Round;
pen.StartCap = LineCap.RoundAnchor;
pen.Width = (int) radius/14;
//--End Minute Hand
The drop shadow of the second hand is nothing but another second hand.
//Drop shadow centre.Offset(2, 2); pen.Color = Color.Gray; Point minHandShadow = new Point( (int)( radius * Math.Sin(minuteAngle) ), (int)( -(radius) * Math.Cos(minuteAngle)+2) ); if(this.drawMinuteHandShadow) { pen.Color = minuteHandDropShadowColor; grfx.DrawLine(pen, centre, minHandShadow); } centre.X = centre.Y = 0; pen.Color = minHandColor; Point minHand = new Point( (int)( radius * Math.Sin(minuteAngle) ), (int)( -(radius) * Math.Cos(minuteAngle)) ); grfx.DrawLine(pen, centre, minHand); }
Similar technique prevails for the hour and minute hand.
How to use the Analog Clock.
Add the AnalogClock
control to your VS.NET toolbox. Then place the analog clock and a timer to your form. Set an appropriate interval for the timer and enable it. Double click the timer and add the following code:
analogClock1.Time = DateTime.Now;
List of Properties for the Analog Clock control
DrawDropShadow
- Determines whether drop shadow for the clock is drawn or not.DrawHourHand
- Determines whether the hour Hand is shown.DrawHourHandShadow
- Determines whether the hour hand casts a drop shadow for added realism.DrawMinuteHand
- Determines whether the minute hand is shown.DrawMinuteHandShadow
- Determines whether the minute hand casts a drop shadow for added realism.DrawNumerals
- Determines whether the numerals are drawn on the clock face.DrawRim
- Determines whether the clock rim is drawn or not.DrawSecondHand
- Determines whether the second hand is shown.DrawSecondHandShadow
- Determines whether the second hand casts a drop shadow for added realism.DropShadowColor
- Sets or gets the color of the drop Shadow.DropShadowOffset
- Gets or sets the drop shadow offset.FaceColorHigh
- Determines the first color of the clock face gradient.FaceColorLow
- Determines the second color of the clock face gradient.FaceGradientMode
- Gets or sets the direction of the clock face gradient.FaceImage
- The Background image used in the clock face.HourHandColor
- Gets or sets the color of the hour hand.HourHandDropShadowColor
- Sets or gets the color of the hour hand drop shadow.MinuteHandColor
- Gets or sets the color of the minute hand.MinuteHandDropShadowColor
- Sets or gets the color of the minute hand drop shadow.MinuteHandTickStyle
- Defines the minute hand tick style.NumeralColor
- Sets or gets the color of the clock numerals.RimColorHigh
- Determines the first color of the rim gradient.RimColorLow
- Determines the second color of the rim face gradient.RimGradientMode
- Gets or sets the direction of the rim gradient.SecondHandColor
- Gets or sets the color of the seconds hand.SecondHandDropShadowColor
- Sets or gets the color of the second hand drop shadow.SecondHandEndCap
- Determines the seconds hand end line shape.SecondHandTickStyle
- Defines the second hand tick style.SmoothingMode
- Sets or gets the rendering quality of the clock.TextRenderingHint
- Sets or gets the text rendering mode used for the clock numerals.Time
- TheSystem.DateTime
structure which is used to display time.
Know Problems/Issues
-
Using a large image for the
FaceImage
property results in poor performance and increased memory consumption, since the image is redrawn every time theTime
property is changed. -
Using large font size on a large clock results in numerals going out the clock face. I haven't given much attention to this detail. This is a design flaw.
-
The width of the rim remains constant and doesn't change with the size of the clock. This is yet another design flaw.
-
The numeral font size doesn't change automatically with the size of the clock. This hasn't been implemented.
转载:http://www.codeproject.com/Articles/10627/Yet-Another-Analog-Clock