Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to build a Clock control

0.00/5 (No votes)
9 Sep 2002 1  
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:

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:

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.

//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.

//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

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.

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:

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