VB.NET 1.1 Tutorial - GDI+ - Analog Clock


So we should have enough of a grasp on GDI to implement our analog clock now. I saw a really nice looking clock on Codeproject, so my inspiration has come from there (but not the code itself, one must learn to do things for oneself).

The Frame

The clock frame consists of two ellipses drawn with a LinearGradientBrush. The colours of the Inner Rim are just the Outer Rim colours in reverse.

' Create Rectangle To Limit brush area.
Dim rect As New Rectangle(20, 20, 230, 230)

' Create Brush
Dim linearBrush As New LinearGradientBrush( _
    rect, _
    Color.FromArgb(0, 0, 0), _
    Color.FromArgb(120, 120, 255), _
    225 )

' Draw Outer Rim to screen.
g.FillEllipse( linearBrush, 20, 20, 200, 200 )

linearBrush.LinearColors = New Color() { Color.FromArgb(120, 120, 225), _
    Color.FromArgb(0, 0, 0) }

' Draw Inner Rim to screen.
g.FillEllipse( linearBrush, 30, 30, 180, 180 )

The Face

A third ellipse is added with the colours of the face, the same as the outer rim.

linearBrush.LinearColors = New Color() { Color.FromArgb(0, 0, 0), _
    Color.FromArgb(120, 120, 255) }

' Draw face to screen.
g.FillEllipse( linearBrush, 33, 33, 174, 174 )

The Numerals

The numbers are drawn over the face. In order to draw them in the right place, I drew two white ellipses and made sure that the numerals looked correct between them. Then I removed the white ellipses.

' Create Brush
Dim numeralBrush As New SolidBrush( Color.White )

/' Create Font
Dim textFont As New Font( "Arial Bold", 12F )

'g.DrawEllipse( New Pen(Color.White, 1), 40, 40, 160, 160 )
'g.DrawEllipse( New Pen(Color.White, 1), 60, 60, 120, 120 )

' Draw Numerals
g.DrawString( "12", textFont, numeralBrush, 109, 40 )
g.DrawString( "11", textFont, numeralBrush, 75, 50 )
g.DrawString( "10", textFont, numeralBrush, 47, 75 )
g.DrawString( "9", textFont, numeralBrush, 43, 110 )
g.DrawString( "8", textFont, numeralBrush, 52, 145 )
g.DrawString( "7", textFont, numeralBrush, 75, 170 )
g.DrawString( "6", textFont, numeralBrush, 113, 180 )
g.DrawString( "5", textFont, numeralBrush, 150, 170 )
g.DrawString( "4", textFont, numeralBrush, 173, 145 )
g.DrawString( "3", textFont, numeralBrush, 182, 110 )
g.DrawString( "2", textFont, numeralBrush, 173, 75 )
g.DrawString( "1", textFont, numeralBrush, 150, 50 )

The Hands

The hands are drawn as simple lines. An amount of maths is used to set the second point of each line.

' In order to draw the hands, we need to Translate to the center of the clock.
g.TranslateTransform( 120, 120, MatrixOrder.Append )

' Get the current time
Dim hour As Integer = DateTime.Now.Hour
Dim min As Integer = DateTime.Now.Minute
Dim sec As Integer = DateTime.Now.Second

' Create Pens
Dim hourPen As New Pen(Color.White, 2)
Dim minutePen As New Pen(Color.LightGray, 2)
Dim secondPen As New Pen(Color.Red, 1)

' Create angles
Dim secondAngle As Single = 2.0 * Math.PI * sec/60.0
Dim minuteAngle As Single = 2.0 * Math.PI* ( min + sec/60.0 )/60.0
Dim hourAngle As Single = 2.0 * Math.PI * ( hour + min/60.0)/12.0

' Set centre point
Dim centre As New Point(0, 0)

' Draw Hour Hand
Dim hourHand As New Point( Convert.ToInt32( 40 * Math.Sin(hourAngle) ), _
                           Convert.ToInt32( -40 * Math.Cos(hourAngle) ) )
g.DrawLine(hourPen, centre, hourHand)

' Draw Minute Hand
Dim minHand As New Point( Convert.ToInt32( 70 * Math.Sin(minuteAngle) ), _
                          Convert.ToInt32( -70 * Math.Cos(minuteAngle) ) )
g.DrawLine(minutePen, centre, minHand)

' Draw Second Hand
Dim secHand As New Point( Convert.ToInt32( 70 * Math.Sin(secondAngle) ), _
                          Convert.ToInt32( -70 * Math.Cos(secondAngle) ) )
g.DrawLine(secondPen, centre, secHand)

Double Buffering

If an application has moving elements on screen, then to avoid flicker, we must enable double-buffering. Visual Basic has an in-built mechanism that can be added to the Form class' constructor, to take care of this for us. The three lines below will take care of this.

For more on double-buffering, see the references below, or check out Bob Powell's article here.

Public Sub New()
  MyBase.New()

  InitializeComponent()

  ' Enable Double Buffering to remove flicker
  Me.SetStyle(ControlStyles.AllPaintingInWmPaint, true)
  Me.SetStyle(ControlStyles.UserPaint, true)
  Me.SetStyle(ControlStyles.DoubleBuffer, true)
End Sub

Invalidation

To redraw the screen we call the Invalidate() method of the Form class, which derives from System.Windows.Forms.Control.

Improving The Hands

OK, it works, but the hands look a bit naff. So let's improve the Hour and Minute hand.

The Hour Hand

Often clocks have a simple arrow head with a round base as the hands, so that is what I decided to create. In order to draw the GraphicsPath object, we calculate the points using trigonometry before we add them to the GraphicsPath.

Dim hourBrush As New SolidBrush(Color.White)

. . .

' Draw Hour Hand
Dim gpHour As New GraphicsPath()
Dim HourArrow As Point() = { _
  New Point( Convert.ToInt32(40 * Math.Sin(hourAngle)), _
             Convert.ToInt32(-40 * Math.Cos(hourAngle)) ), _
  New Point( Convert.ToInt32(-5 * Math.Cos(hourAngle)), _
             Convert.ToInt32(-5 * Math.Sin(hourAngle)) ), _
  New Point( Convert.ToInt32(5 * Math.Cos(hourAngle)), _
             Convert.ToInt32(5 * Math.Sin(hourAngle)) ), _
  New Point( Convert.ToInt32(40 * Math.Sin(hourAngle)), _
             Convert.ToInt32(-40 * Math.Cos(hourAngle))) }

gpHour.AddPolygon(HourArrow)
g.FillPath( hourBrush, gpHour )
g.FillEllipse( hourBrush, -5, -5, 10, 10 )

This code makes the following change to the clock.

The Minute Hand

This is drawn in a very similar way as the Hour hand, but instead of using a white brush, I have used a Light Grey brush to differentiate between them. Also, the length of the hand is increased from 40 to 70.

Dim minuteBrush As New SolidBrush(Color.LightGray)

The clock looks like this when we add the Minute hand..

The Second Hand

This has not changed in any way, so the finished clock now looks like this.

Adding the Tick

At this point, the clock just draws the current time when the executeable is run. So lets add a timer to the application.

There are more than one timer in the .net framework. The one I am going to use here is from System.Windows.Forms.Timer. To add a timer follow these simple steps. Firstly add a new timer in the top of the class.

Private myTimer As New Timer()

In the InitializeComponent() method, add the following lines to setup the timer. The timer interval is set to 1000 milliseconds.

' ' myTimer
'
' Adds the event and the event handler for the method that will
' process the timer event to the timer.
AddHandler myTimer.Tick, AddressOf TimerEventProcessor

myTimer.Enabled = True
myTimer.Interval = 1000
myTimer.Start()

Finally add the event handler to redraw the clock every second with the current time.

' This is the method to run when the timer is raised.
Private Sub TimerEventProcessor(myObject As Object, myEventArgs As EventArgs)
  ' Redraw the clock
  Me.Invalidate()
End Sub

Now we have a moving analog clock.

You can download the file analog.vb from the link, or you can download my copy of the executable from here.


References

For more information on the TextBox class, visit MSDN at microsoft here.

For more information on the TranslateTransform method, visit MSDN at microsoft here.

For more information on the ControlStyles enumeration, visit MSDN at microsoft here.

For more information on Invalidate() method, visit MSDN at microsoft here.

For more information on System.Windows.Forms.Timer, visit MSDN at microsoft here.

What Next?

Return to the Tutorial Contents.