Click here to Skip to main content
15,868,340 members
Articles / Programming Languages / C#
Article

How to build a Clock control

Rate me:
Please Sign up or sign in to vote.
3.67/5 (9 votes)
9 Sep 20026 min read 105.5K   3.1K   52   6
This is a quick guide on how to create your own customizable clock control

Introduction

In this tutorial I will build a very customizable clock. I will explain the code as much as I can so you will understand it easily. Don't get impressed by the amount of code because it is easy to understand and I have commented almost every line.

Building The Control

First start Microsoft Visual C# and create a new Windows Application Project. From the File Menu select Add New Item and choose Custom Control from the list. From the ToolBox drag a Timer onto the Design of the CustomContol. Rename the control to "Clock" and the Timer to "timer". Set the Interval value of the timer to 1000 (The refreshing time of the Clock).

Right Click on the Design of the Clock and choose View Code. In the Clock constructor Add the following lines of code:

C#
public Clock()
{
    //Set Up the Refresh Timer
    timer = new System.Windows.Forms.Timer();
    timer.Interval = 1000;
    timer.Enabled = true;
    timer.Tick+= new System.EventHandler(this.Timer_Tick);
}

The new statement creates the Timer. We set it's interval to 1000 milliseconds, enable it and create a new Event Handler of the Tick Event. This means that the Timer_Tick function is Called after each Interval of the Timer.

Now it's time to create the Timer_Tick Function. After the constructor, add the following lines of code:

C#
private void Timer_Tick(object Sender, EventArgs e)
{
    //Refresh the Clock
    this.Invalidate();
    this.Update();
}

Call the Invalidate function when you want the control to repaint itself.

Next it`s time to declare the Parameters of the Clock. I will explain later in this Tutorial what each one does. Add them from the source code after the GetPoint function

In the next section I will explain what do all those variables do. Read the names of the variables carefully to understand what they represent. If you syill have trouble understanding what they mean read the comments above each one and look at the picture above.It`s time to initialize all of those parameters in the constructor. Add these lines of code after the initialization of the timer.

C#
//The point in the center of the clock
m_ptCenterPoint = new Point(70,70);

//The Radius of the perimeter pots
m_nClockRadius = 50;
//The Brush used to draw the perimeter dots
m_brPerimeterPoints = Brushes.Black;
//The Diameter of the small dots of the perimeter
m_nSmallDotSize = 2;
//The Diameter of the big dots of the perimeter
m_nBigDotSize = 4;
//The Offset of the perimeter numbers
m_nNumberOffset = -5;
//The Radius of the perimeter numbers
m_nNumberRadius = m_nClockRadius + 12;

//The Font used to draw the perimeter numbers
m_ftNumbers = new Font("Verdana",8);
//The Font used to draw the Bottom Number Clock
m_ftNumberClock = new Font("Verdana",8);
//Boolean value indicating if the perimeter numbers are visible
m_bNumbers = true;
//The Brush used to draw the perimeter numbers
m_brNumbers = Brushes.Black;
//The Length of the Hour Pointer
m_nHourPointerLength = 30;
//The Length of the Minute Pointer
m_nMinutePointerLength = 40;
//The Length of the Second Pointer
m_nSecondPointerLength = 45;

//The Brush Size of the Hour Pointer
m_nHourPointerSize = 10;
//The Brush Size of the Minute Pointer
m_nMinutePointerSize = 8;
//The Brush Size of the Second Pointer
m_nSecondPointerSize = 6;

//The Brush used to draw the Hour Pointer
m_brHourPointer = Brushes.Blue;
//The Brush used to draw the Minute Pointer
m_brMinutePointer = Brushes.Red;
//The Brush used to draw the Second Pointer
m_brSecondPointer = Brushes.Green;
//Boolean value indicating if the Bottom Number Clock is Visible
m_bNumberClock = true;

//Boolean value indicating if the Background Image is visible
m_bBackImage = true;

I initialized the variables to default values so that if they are not changed they have a default value.
Be careful with the m_imgBackGround because it is not initialized here. I will set a value to it in the Form, when I create the Clock.

Now here comes the tricky part, the Paint function

Add the lines of code in your Overloaded Paint function after the base.OnPaint(pe); statement.
The lines are self explanatory if you read the comments above each one. Don`t get confused about the long code because I will explain it step by step. You will notice the use of a GetPoint function. I will write the code of that function later as well as explain it to you.

C#
//Set the Offsets
int offsetx = m_nOffset;
int offsety = m_nOffset;

//Copy the m_ptCenterPoint in centerPoint for use in this Function
Point centerPoint = m_ptCenterPoint;
//if the Boolean Back Image is true it draws the Back Image to fit the 
// Client Rectangle of the control
if (this.m_bBackImage)
    pe.Graphics.DrawImage(this.imgBackGround,0,0,ClientRectangle.Width,
                          ClientRectangle.Height);

//This for statement Draws the perimeter Dots and Numbers
for(int i=1;i<=60;i++)
{
    //This is the Angle of the Current Number to Draw
    //I calculate this Angle by a formula that I came up with after a good thinking process
    //I will use it in other ways to calculate the different angles that I need
    float NumberAngle =360-(360*(i/5)/12)+90;
    //Copy the NumberRadius for use in this function
    int NumberRadius = m_nNumberRadius;
    //Calculate the Pozition of the Number
    Point NumberPoint = GetPoint(centerPoint,NumberRadius,NumberAngle);

    //This is the Angle of the Current Dot
    float DotAngle =360-(360*(i)/60)+90;
    //Copy the Dot Radius for use in this function
    int DotRadius = m_nClockRadius;
    //Calculate the Point of the Dot
    Point DotPoint = GetPoint(centerPoint,DotRadius,DotAngle);

    //Copy the DotSizes for use in this function
    int SmallDotSize = m_nSmallDotSize;
    int BigDotSize = m_nBigDotSize;
    
    //Draws the current small point
    pe.Graphics.FillEllipse(m_brPerimeterPoints,DotPoint.X-SmallDotSize/2, 
                            DotPoint.Y-SmallDotSize/2,SmallDotSize,SmallDotSize);

    //if it`s a big Dot
    if (i%5==0)
    {
        //if the Numbers are Visible Draw them at the calculated position
        if (m_bNumbers)
            pe.Graphics.DrawString((i/5).ToString(),m_ftNumbers,m_brNumbers,
                         NumberPoint.X+m_nNumberOffset,NumberPoint.Y+m_nNumberOffset);
        //Draw the Big Dots
        pe.Graphics.FillEllipse(m_brPerimeterPoints,DotPoint.X-BigDotSize/2,
                         DotPoint.Y-BigDotSize/2,BigDotSize,BigDotSize);
    }
}    

//Get the Current Local Time
DateTime dt = DateTime.Now;

//calculate the min value for use in the HourAngle
float min = ((float)dt.Minute)/60;
//Calculate the Angle of the Hour Pointer
float HourAngle =360-(360*(dt.Hour+min)/12)+90;
//Calculate the Angle of the Minute Pointer
float MinuteAngle =360-(360*dt.Minute/60)+90;
//Calculate the Angle of the Second Pointer
float SecondAngle =360-(360*dt.Second/60)+90;
            
//Calculate the EndPoint of the Hour Pointer
Point HourEndPoint = GetPoint(centerPoint,m_nHourPointerLength,HourAngle);
//Calculate the EndPoint of the Minute Pointer
Point MinuteEndPoint = GetPoint(centerPoint,m_nMinutePointerLength,MinuteAngle);
//Calculate the EndPoint of the Second Pointer
Point SecondEndPoint = GetPoint(centerPoint,m_nSecondPointerLength,SecondAngle);

//Copy the Sizes for use in this function
int SecondSize = m_nSecondPointerSize;
int MinuteSize = m_nMinutePointerSize;
int HourSize   = m_nHourPointerSize;

//Draw the Second Pointer Line
pe.Graphics.DrawLine(new Pen(m_brSecondPointer,SecondSize),centerPoint,SecondEndPoint);
//Draw the Second Pointer Top
pe.Graphics.FillEllipse(m_brSecondPointer,SecondEndPoint.X-SecondSize/2,
                        SecondEndPoint.Y-SecondSize/2,SecondSize,SecondSize);

//Draw the Minute Pointer Line
pe.Graphics.DrawLine(new Pen(m_brMinutePointer,MinuteSize),centerPoint,MinuteEndPoint);
//Draw the Minute Pointer Top
pe.Graphics.FillEllipse(m_brMinutePointer,MinuteEndPoint.X-MinuteSize/2,
                        MinuteEndPoint.Y-MinuteSize/2,MinuteSize,MinuteSize);

//Draw the Hour Pointer Line
pe.Graphics.DrawLine(new Pen(m_brHourPointer,HourSize),centerPoint,HourEndPoint);
//Draw the Hour Pointer Top
pe.Graphics.FillEllipse(m_brHourPointer,HourEndPoint.X-HourSize/2,
                        HourEndPoint.Y-HourSize/2,HourSize,HourSize);

//The size of the Center Point
int CenterPointSize = m_nHourPointerSize;
//Draw the Center Point to cover the ends of the Pointers
pe.Graphics.FillEllipse(Brushes.Black,centerPoint.X-CenterPointSize/2,
                       centerPoint.Y-CenterPointSize/2,CenterPointSize,CenterPointSize);

//if the Number Clock is Visible Draw It
if (m_bNumberClock)
    pe.Graphics.DrawString(String.Format("{0:}:{1:}:{2:}",dt.Hour,dt.Minute,dt.Second),
                           m_ftNumberClock,Brushes.Red,centerPoint.X-35*m_ftNumberClock.Size/12,
                           centerPoint.Y+m_nNumberRadius + m_ftNumbers.Size+5);

I will now explain what I have done.

I am using the pe.Graphics to receive the Graphics object of the Clock. At first I draw the Background image stored in the m_imgBackGround variable but only if the user wants to (the if statement).

Then, in the for statement I draw the 60 dots on the clock calculating their position by the formula there. Don't worry about the formula because I will explain it later in this article .If 5 divides the counter(i) then that means that that is an important dot and we must draw a big dot and the number corresponding with (i/5). The numbers have an offset because the point calculated is in the middle of the text ant I need a point at the top-left of the number. That is what I have done in the if (i%5==0) statement.

Next it`s time to draw the Pointers themselves. I get the Curent time (

DateTime 
  dt = DateTime.Now;
) in the dt variable;

I will now explain how the formula of calculating the angle of the Hour Pointer works. You can skip this part if you want to.

float HourAngle =360-(360*(dt.Hour)/12)+90;

Let`s take it from the center and look at the 360*(dt.Hour)/12 part for now. Let's say that it's 6 o'clock (half of 12). this expression would return 180 wich means half of 360. If it was 3 o'clock (a quarter of 12) the expression would return 90 wich is a quarter of 360. As you can see this isn`t the real angle of the pointer. This is because the direction of the angles is counter clockwise. This is why I subtracted the expression from 360 to make it clockwise. This is still not the angle that we are looking for because on the circle "0" is on the right wile on the clock "0"(12) is on top (a 90 degrees difference). This is why I added 90. The formula isn't still as the one in the code which is:

float min = ((float)dt.Minute)/60;
360-(360*(dt.Hour+min)/12)+90;

The min variable is, as you can see always smaller than 1. I transformed the minutes in 0.something proportional to the Minutes. I added min to dt.Hour so that the Hour Pointer tilts towards the next Hour as the minutes go by, it doesn't stick on it's position.

Next I calculate the Minute and Second Angles by the same formula ,and the End Points of each Pointer with the GetPoint function that I will explain later.

Next I draw the pointers with their specific Widths kept in

SecondSize 
and the other, that are equal to the member variables m_nSecondPointerSize; through the lines of code:int SecondSize = m_nSecondPointerSize; I draw the pointers having the centerPoint as the first point and the calculated end points SecondEndPoint and their brushes using:

pe.Graphics.DrawLine(new Pen(m_brSecondPointer,SecondSize),centerPoint,SecondEndPoint);

After each Pointer I draw an elipse on the tip of the pointers to round their aspect. It has the diameter equal to the thickness of it`s pointer. Then I draw the big dot in the Center, and as the last piece, I draw the number clock at the bottom.

Now it's time for the GetPoint function

C#
public Point GetPoint(Point ptCenter, int nRadius, float fAngle)
{
    float x = (float)Math.Cos(2*Math.PI*fAngle/360)*nRadius+ptCenter.X;
    float y = -(float)Math.Sin(2*Math.PI*fAngle/360)*nRadius+ptCenter.Y;
    return new Point((int)x,(int)y);
}

Let me explain this now.

Think of the circle. If you have a point on the circle and you know it's angle (fAngle) then it's coordinates are Cos(fAngle) and Sin(fAngle). In the Math.Cos(2*Math.PI*fAngle/360) Cos statement I transform from Degrees to Radians. We now have the coordinates of the point on the circle but our circle's radius is different from 1 so I multiply the coordinates with the radius. I then add the center`s coordinates because the point of origin on our screen is on the top-left while on the circle is in the center of the circle.

The last piece of the control is the ScaleToFit function.

C#
public void ScaleToFit(System.Drawing.Size sSize)
{
    float ScaleFactor = (float)sSize.Width/(140);
    m_nClockRadius =(int)(50*ScaleFactor);
            
    m_nOffset = 0;//(int)(20*ScaleFactor);


    m_nSmallDotSize = (int) ( 2*ScaleFactor);
    m_nBigDotSize = (int) ( 4*ScaleFactor);
    m_nNumberOffset = (int) ( -5*ScaleFactor);
    m_nNumberRadius = (int) ( m_nClockRadius + 12*ScaleFactor);

    m_ftNumbers = new Font("Verdana",(int)(8*ScaleFactor));
    m_ftNumberClock = new Font("Verdana",(int)(8*ScaleFactor));

    m_nHourPointerLength = (int) ( 30*ScaleFactor);
    m_nMinutePointerLength = (int) ( 40*ScaleFactor);
    m_nSecondPointerLength = (int) ( 45*ScaleFactor);

    m_nHourPointerSize = (int) ( 10*ScaleFactor);
    m_nMinutePointerSize = (int) ( 8*ScaleFactor);
    m_nSecondPointerSize = (int) ( 6*ScaleFactor);

    m_ptCenterPoint.X = (int)(sSize.Width/2);
    m_ptCenterPoint.Y = (int)(sSize.Width/2);

    this.m_sSize = sSize;
}
It scales all of the clock`s numerical properties by the ScaleFactor so that the clock will fit into the specified Size object.

Now the control is ready to use but it isn't included in the Main form. I will show you how to do this. Go to the Form1[Design] tab ,right click and choose View Code. In the Constructor, after the InitializeComponent(); function add the folowing lines of code:

C#
Clock clock = new Clock();
clock.Location = new Point(0,0);
clock.Size = new Size(300,300);
clock.bBackImage = false;
this.Controls.Add(clock);

If you want the clock to have a Background image replace the line :

clock.bBackImage
 = false;
with the line :

clock.imgBackGround = Image.FromFile("/*the path name to a file*/");

If you want to scale the clock you just have to call the clock.ScaleToFit function and give it a size ofject for the clock to fit in.

If you want to modify the properties of the clock do so by calling the clock.propertyname and set it another value.

Now Run the project and enjoy the nice clock.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Romania Romania
I am 18 years old and I have programmed for one year in C++ and MFC.
I am now programming in C# and .NET Framework.

Comments and Discussions

 
GeneralNice Control Pin
Armoghan Asif8-Aug-05 22:16
Armoghan Asif8-Aug-05 22:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.