Yet Another Analog Clock

Sample screenshot

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 Wink | ;-) ). 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:

Sample screenshot

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.

Sample screenshot

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:

Sample screenshot

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:

Sample screenshot

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.

Sample screenshot

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 - The System.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 the Time 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值