Click here to Skip to main content
Email Password   helpLost your password?

Sample Image - clock_picture.gif

Introduction

We are going to be taking a look at the construction of an analog clock using textured backgrounds. I know this "looks" hard and intimidating, but believe me, it's anything but hard :-)

Step 1

Modifying your form's startup

First thing is, we need to change the way your form starts so we can control the loading procedure. This is fairly simple to accomplish. What you need to do is create a Shared Sub Main in your main program and set that as the startup. Don't forget to name your form frmClock.

    Shared Sub Main()
        Application.Run(New frmClock())
        Application.Exit()
    End Sub

This will start up the form named frmClock. Then, when it's done, exit the application. Now, we need to modify the new procedure for the form and insert a me.show command and an initClock command.

    Public Sub New()
        MyBase.New()
        InitializeComponent()
        initclock()
    End Sub

All that's left to do now is change the project's startup properties to make it use our Shared sub Main and not the standard startup procedure. Go to Solution Explorer and right click on your project's properties and change the Startup Object.

Step 2

Create the cHands class to hold the hands information

01:    Public Class cHands
02:    Private Angle As Integer = 0
03:    Public Path As New Drawing2D.GraphicsPath()
04:    Public filled As Boolean = False
05:
06:
07:    ' We have to set a new angle so subtract it from the old one and thats 

08:    ' how far we have to move

09:    '

10:    Public Sub setAngle(ByVal newAngle As Integer, ByVal imgCenter As PointF)
11:        Dim Rotate As Integer = newAngle - Angle
12:        Dim trans As New Drawing2D.Matrix()
13:        trans.RotateAt(Rotate, imgCenter)
14:        Path.Transform(trans)
15:        Me.Angle = newAngle
16:    End Sub
17:
18:
19:    ' Did somone say draw?

20:    '

21:    Public Sub draw(ByVal imgDraw As Graphics)
22:        If filled Then
23:            imgDraw.FillPath(Brushes.DimGray, Path)
24:            imgDraw.DrawPath(Pens.Black, Path)
25:        Else
26:            imgDraw.DrawPath(New Pen(Color.DimGray, 2), Path)
27:        End If
28:    End Sub
29: End Class
Line 1
Declare the class name.
Lines 2-4
Set up some internal variables, one to track the last angle, one to hold the graphics object and another to hold the value if we want to fill the lines.
 
Line 10
Create Sub setAngle that will take the new angle of the hand minus the old angle on the hand and rotate the object the rest of the distance.
Line 11
Figure out how much farther the line needs to rotate to get to where it needs to be.
Line 12
Create a new Transform Matrix object.
Lines 13-14
Rotate the hand the distance it needs to go and set the pivot point (rotation point) at the center of the circle.
Line 15
Set the old angle to the new angle.
Line 16
End Sub
 
Line 21
Create Sub Draw that will draw your paths.
Line 22-25
If you want the hands filled, fill them in and draw a black border around them to look nice.
Lines 26-27
Draw the line with a thickness of two, but don't fill it in.
Line 28
End Sub.

In this step, we have created a class that will be used to store information about the individual hands of the clock.

Step 3

Controls

Go to your form, add a Timer and a PictureBox control. Set the PictureBox to the name clock with a background color of black and set it as docked with the entire form. Leave the Timer control as timer1 and enable it.

Step 4

Tick tock goes the clock

First thing we need to do is create the variables for the clock hands using our cHands class and a point that will denote the center of the screen.

    Private handHour As New cHands()
    Private handMinute As New cHands()
    Private handSecond As New cHands()
    Private center = New PointF(150, 150)

Next is the part where it all starts, the initclock Sub.

    Private Sub initclock()
        Me.ClientSize = New Size(300, 300)

First things first, make sure the client area for the form is 300 x 300. Now, we initialize the hands, remember the Path object in cHands? Well, it's come time to set it, but first a few notes. We will always set the hand straight up (at the 12:00 position). From here, it will be rotated from the center of the screen, so it will look as if it is turning like a real clock. We draw our lines with points, point a connects to point b, point b connects to point c, and point c connects again with point a. These points are contained within an array of points. This can look quite overwhelming but really makes perfect sense if you step through it slowly.

    Dim drawHour As Point() = _
        {New Point(Int(clock.Width / 2), Int(clock.Height / 2)), _
        New Point(Int(clock.Width / 2 - 5), 80), _
        New Point(Int(clock.Width / 2), 50), _
        New Point(Int(clock.Width / 2 + 5), 80), _
        New Point(Int(clock.Width / 2), Int(clock.Height / 2))}
                                   
    Dim drawMinute As Point() = _
       {New Point(Int(clock.Width / 2), Int(clock.Height / 2)), _
        New Point(Int(clock.Width / 2 - 5), 60), _
        New Point(Int(clock.Width / 2), 30), _
        New Point(Int(clock.Width / 2 + 5), 60), _
        New Point(Int(clock.Width / 2), Int(clock.Height / 2))}

Why didn't we use center instead of New Point(Int(clock.Width / 2), Int(clock.Height / 2))? Well, center is a PointF (Floating point or Single) and this requires a point (integer). Now we have to associate these values with the Path object.

        handHour.Path.AddLines(drawHour)
        handHour.filled = True
        
        handMinute.Path.AddLines(drawMinute)
        handMinute.filled = True

But wait, what about the second hand? Well, the second hand is just a line. It's not a shape that needs to be filled. So, it's really easy to draw.

        handSecond.Path.AddLine(center, New PointF(Int(clock.Width / 2), 25))
    End Sub

That's it for this Sub. Now, on to the clock timer (its really complicated :-)).

    Private Sub Timer1_Tick(ByVal sender As System.Object, _
                 ByVal e As System.EventArgs) Handles Timer1.Tick
        clock.Invalidate()
    End Sub

Basically, what this says is every time the timer ticks, repaint the clock. Now we need to create the painting event for clock.

    Private Sub clock_Paint(ByVal sender As Object, _
          ByVal e As System.Windows.Forms.PaintEventArgs) Handles clock.Paint
        handHour.setAngle((360 / 60) * Val(Format(Now, "hh")), center)
        handMinute.setAngle((360 / 60) * Val(Format(Now, "mm")), center)
        handSecond.setAngle((360 / 60) * Val(Format(Now, "ss")), center)

        handHour.draw(e.graphics)
        handMinute.draw(e.graphics)
        handSecond.draw(e.graphics)
    End Sub

What this Sub does is fairly simple. It calculates the degrees the hands need to be at, then calls the draw Sub for the corresponding object.

Why not run your program now and watch the hands move across the screen?

Step 5

Dressing it up

Here, you create a Sub named drawFace. You can add anything you want to it. I opted to fill in the ellipses with textures instead of gradients (look better that way).

    Private Sub drawFace(ByVal g As Graphics)
        Dim path As String = Mid(Application.ExecutablePath, 1, _
                          InStrRev(Application.ExecutablePath, "\"))
        Dim grdFace As New TextureBrush(New Bitmap(path & "\face.jpg"))
        Dim grdWatch As New TextureBrush(New Bitmap(path & "\watch.jpg"))

        g.FillEllipse(grdWatch, New Rectangle(5, 5, 290, 290))
        g.FillEllipse(Brushes.Black, New Rectangle(15, 15, 270, 270))
        g.FillEllipse(grdFace, New Rectangle(20, 20, 260, 260))

        Dim f As New Font("Arial", 10, FontStyle.Bold)
        Dim Sze = g.MeasureString("Powered by Visual Basic.NET", f)
        g.DrawString("Powered by Visual Basic.NET", f, _
                     Brushes.Black, (300 - Sze.width) / 2, 200)

        grdFace.Dispose()
        grdWatch.Dispose()

        ' Don't dispose g !
    End Sub

The last thing you need to do is add the drawFace command to your paint Sub like this:

    Private Sub clock_Paint(ByVal sender As Object, _
         ByVal e As System.Windows.Forms.PaintEventArgs) Handles clock.Paint
       drawFace(e.Graphics)

       ...
       ...
       ...

    End Sub

Well, that's it, you should now have a working clock with a cool looking face.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMy vote of 2
Shane Story
5:05 12 May '09  
The code looks like one of the MS sample projects. Someone else mentioned it looked like Vb.NET Hacks & Pranks. I guess the article is OK, but wonder about the origin.
GeneralPretty coo'
xasthom
13:40 31 May '07  
Haha I recognize this example Big Grin It's from 'VB.NET Hacks & Pranks' by Alexander Klimov, easily the most useful .NET book I ever bought *gasps* and it was only £15! Hehe you even kept the variable names the same
GeneralRe: Pretty coo'
Matthew Hazlett
14:10 31 May '07  
I wrote this myself...


Matthew Hazlett

Sometimes I miss the simpler DOS days of Borland Turbo Pascal (but not very often).

GeneralNice clock!
Hamfiles
16:59 27 May '07  
Thank you for posting this code. I realize that this posting is somewhat dated, but I would like to use a modified verison of your cHands class in one of my projects.
GeneralCompiles but ERR's
smitsc
8:08 24 Feb '06  
code compiles OK but I get an error in the shared sub main "Application.Run(New frmClock)" Invalid Parameter Used in system.windows.forms.dll

my email has changed to smitsc@sbcglobal.net

Thanks

Scott S.
GeneralRe: Compiles but ERR's
Pav999
7:34 13 May '06  
Move the face.jpg and the watch.jpg files into the same directory as the exe file.
or
change the Sub drawFace routine.
GeneralIs possible make this clock
akorolev10
21:50 12 Oct '04  
see url
http://evert.meulie.net/e107_plugins/flashclock_menu/clock.swf[^]

test
GeneralRe: Is possible make this clock
Evert Meulie
19:26 20 Jan '05  
the correct URL is: http://www.meulie.net/ 8-)
GeneralNot showing exact hour hand
Karthikeyan Muthurajan
0:31 13 Aug '04  
Hi,
The code has to be change bcos it doesn't show the hour hand correctly.
The angle set for hour hand should be multiply with 5

In the paint event of clock, the code has to be change from
handHour.setAngle((360 / 60) * Val(Format(Now, "hh")), center)
to
handHour.setAngle((360 / 60) * Val(Format(Now, "hh") * 5), center)


GeneralRe: Not showing exact hour hand
John Harald Apeland
13:48 14 Aug '05  
Karthikeyan Muthurajan, you're right about the hours in plain, only '/12' is more logically than '/60*5' - the result is the same, there is 30 degrees of a circle for each hour.

A more correct calculation and behaviour of the analog clock is to also add the elapsed seconds to the minute movement, and add the elapsed minutes (+seconds) to the hour movement.

Think about the hour, the 30 degrees that make up one full hour move can also be divided into 60 minor moves depending on the minute of the hour. With this logic clear in mind, coding it is easy.

Replace in clock_Paint where 'Set the hands':

Dim dt As Date = New Date().Now
Dim s As Integer = (360 / 60 * dt.Second)
Dim m As Integer = (360 / 60 * dt.Minute) + (s / 60)
Dim h As Integer = (360 / 12 * dt.Hour) + (m / 60)
handHour.setAngle(h, center)
handMinute.setAngle(m, center)
handSecond.setAngle(s, center)
GeneralRe: Not showing exact hour hand
John Harald Apeland
14:09 14 Aug '05  
well easy Smile
Dim s As Integer = (360 / 60 * dt.Second)
Dim m As Integer = (360 / 60 * dt.Minute) + (6 / 60 * dt.Second)
Dim h As Integer = (360 / 12 * dt.Hour) + (30 / 60 * dt.Minute) + (30 / 60 / 60 * dt.Second)
GeneralAnalog watch
Jose F. Sosa
13:35 19 May '04  
Big Grin
Hi the code is very cool I'm building a Chronograph watch with Tachometer using these as my starting point and was interested in finding out just how to add numbers with the correct rotation going around the clock and also adding the stop watch functionality. Also I can seam to get the current hour to display correctly it seams its 2 hours behind.


JFS


Tlaloc
GeneralDoesnt Work Too Many Errors
SimplySurfin
11:17 31 Mar '04  
way too many coding errors. this code is not functional. i dont know why people submit code, before testing it. so many times i see that they have to thread thier own fixes, and end up saying " oops, i forgot ". just check it first. another thing i noticed: people give huge explanations of how the code was created, and what each line does, and end up dropping lines, by not even mentioning them. i wish this code thing would get straight. i am so frustrated about how the examples are within all of the forums. yes, i am new to this, but i can soon be un-new. if that is even a word, but i dont think many will mind. they are probably saying the same.

Please submit only what you know works.
GeneralRe: Doesnt Work Too Many Errors
Matthew Hazlett
11:43 31 Mar '04  
This code works great, it has been used by a lot of people the use The Code Project. If you can not get it to work maybe there is a problem with your understanding. If this is the case I'll be glad to show you how to use it.

If you cant get the code to work you can download the sample.


Matthew Hazlett
Windows 2000/2003 MCSE
Never got an MCSD, go figure...
GeneralPower by Visual Basic?
Norm Almond
21:42 8 Jan '04  
you mean .net WTF

I am that is


GeneralBefore you ask :-)
hazlema
16:04 8 Jan '04  
Heres how to draw the minute andd hour lines on the clock, add this to the end of drawFace.

Dim x As integer
For x = 0 To 360 Step 360 / 60
Dim minuteLines As New Drawing2D.GraphicsPath
Dim minuteTrans As New Drawing2D.Matrix

If x Mod 5 = 0 Then _
minuteLines.AddLine(New PointF(150, 30), New PointF(150, 20)) _
Else minuteLines.AddLine(New PointF(150, 25), New PointF(150, 20))

minuteTrans.RotateAt(x, center)
minuteLines.Transform(minuteTrans)

If x Mod 5 = 0 Then _
g.DrawPath(New Pen(New SolidBrush(Color.Black), 2), minuteLines) _
Else g.DrawPath(New Pen(New SolidBrush(Color.Black), 1), minuteLines)

minuteLines.Dispose()
minuteTrans.Dispose()
Next

GeneralRe: Before you ask :-)
andremoraes
9:35 14 Jan '04  
sugestions :
in sub clock_paint :

replace

handHour.setAngle((360 / 60) * Val(Format(Now, "hh")), center)

per

handHour.setAngle(30 * Val(Format(Now, "hh")) + 30 * (Val(Format(Now, "mm")) / 60), center)



Last Updated 8 Jan 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010