Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

An Analog Clock Control

4.77/5 (22 votes)
14 Mar 2009LGPL34 min read 81.3K   5K  
An analog clock control with timezone modification
Analog Clock Control

Introduction

This is my second article for The Code Project community. This article will explain about making an analog clock control that mimics Windows Vista clock sidebar gadget. It mimics the loading and tick effect, has timezone property which can be used to maintain timezone for displaying in the clock, and has a nice designer support feature as well.

TheClock is an analog clock control that mimics Windows Vista sidebar gadget especially in its behaviour like loading, ticks, and even timezones. Take a note that since timezone features will have a complex method, I will separate the article which you can find after I have finished with this article.

Background

With the release of Windows Vista, most people would know that one new thing that comes with it is the sidebar with gadgets. In all those gadgets, there's a control that catches my attention, the analog clock. It has nice features and interface. After observing how it works, I decided to make my own VB analog clock based on that gadget. Thus, I created this article to share with CodeProject members, especially most of members who already contributed their good articles to this site that helped me a lot with my work.

Using the Code

The control has a main component named TheClock. The main objective that this component must have is its ability to tick in a fancy way, just like Windows Vista gadget. The second feature that it should have is Timezone feature. This article will explain about the main component itself. I will discuss Timezone feature separately in another article.

Initialize the Component

The first thing we need to do is to make a component. TheClock will inherit control object and will implement ISupportInitialize because we will add some initial values after placing the control.

VB.NET
#Region "The Clock - Copyright (C) 2007 Michael Rawi"
 'The Clock
'Windows Forms Component - NET Framework 2.0
'Copyright (C) 2007 Michael Rawi

'This library is free software; you can redistribute it and/or
'modify it under the terms of the GNU Lesser General Public
'License as published by the Free Software Foundation; either
'version 2.1 of the License, or (at your option) any later version.

'This library is distributed in the hope that it will be useful,
'but WITHOUT ANY WARRANTY; without even the implied warranty of
'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
'Lesser General Public License for more details.

'Michael Rawi
#End Region
Imports System.DateTime

''' <summary>
''' An analog clock.
''' </summary>
''' <remarks>Created by Michael Rawi</remarks>
<Designer(GetType(TheClockDesigner)), _
Description("A modern analog clock."), _
ToolboxBitmap(GetType(TheClock), "TheClock.png")> _
Public Class TheClock
    Inherits Control
    Implements System.ComponentModel.ISupportInitialize

...
...
...

End Class

Setting up Variables and Properties

Before making a control, you should have known what variable you will need for developing the control. You don't have to know completely since you can have additional variables later, but you should at least define what you will need the most. For this control, we will also need enumerations.

VB.NET
...
...

#Region "Enumeration"
    Public Enum Styles
        Analog = 0
    End Enum

    Public Enum SecondTicks
        [Default] = 0
        Rapid = 1
        Original = 2
    End Enum

    Private Enum FadeStatus
        [In] = 0 'Slowly disappeared - 1.0F~0.5F in 0.5 second by 5 Frame
        Out = 1 'Slowly reappeared
    End Enum
#End Region

#Region "Component Variables"
 Region "Core Variables"
    Private WithEvents _tmrTime As Timer
    Private WithEvents _tmrTick As Timer
    Private WithEvents _tmrLoad As Timer

    Private _imgS As Image
    Private _imgM As Image
    Private _imgH As Image

    Private _angleS As Single
    Private _angleM As Single
    Private _angleH As Single

    Private _ComInfo As New Devices.ComputerInfo
#End Region
#Region "Private Properties"
    Private _secTick As SecondTicks = SecondTicks.Default
    Private _style As Styles
#End Region
#Region "Load Animation"
    Private _loadTimeframe As Single
    Private _loadHAngle As Single
    Private _loadMAngle As Single
    Private _loadSAngle As Single
    Private _loadMod As Integer
#End Region
#Region "Features"
    Private _ShowSecond As Boolean = True
    Private _AdvancedMode As Boolean = False

    Private _FadeFocus As Boolean = True
    Private _FadeStatus As FadeStatus = FadeStatus.Out
    Private _FadeAlpha As Single = 0.5F
    Private WithEvents _tmrFade As Timer

    Private _span As TimeSpan = New TimeSpan(0, 0, 0)
    Private _State As String
#End Region
#End Region

...
...

In the next step, we will create some property value for making design time easier.

VB.NET
...
...

#Region "Properties"
    ''' <summary>
    ''' Get or set the style of the clock.
    ''' </summary>
    ''' <value>Style</value>
    ''' <returns>Current Style</returns>
    ''' <remarks></remarks>
    <Description("Get or set the style of the Clock"), Category("Features"), _
    DefaultValue(GetType(Styles), "Analog")> _
    Public Property Style() As Styles
        Get
            Return _style
        End Get
        Set(ByVal value As Styles)
            _style = value
        End Set
    End Property

    ''' <summary>
    ''' Get or set the second tick's style.
    ''' </summary>
    ''' <value>SecondTick</value>
    ''' <returns>Current SecondTicks</returns>
    ''' <remarks></remarks>
    <Description("Get or set the second tick's style"), Category("Features"), _
    DefaultValue(GetType(SecondTicks), "Default")> _
    Public Property SecondTick() As SecondTicks
        Get
            Return _secTick
        End Get
        Set(ByVal value As SecondTicks)
            _secTick = value
            Select Case value
                Case SecondTicks.Rapid
                    _tmrTime.Interval = 100
                Case Else
                    _tmrTime.Interval = 1000
            End Select
        End Set
    End Property

    ''' <summary>
    ''' Get or set the second visibility.
    ''' </summary>
    ''' <value>Boolean</value>
    ''' <returns>Second visibility</returns>
    ''' <remarks></remarks>
    <Description("Get or set the second visibility"), Category("Features"), _
    DefaultValue(True)> _
    Public Property ShowSecond() As Boolean
        Get
            Return _ShowSecond
        End Get
        Set(ByVal value As Boolean)
            _ShowSecond = value
        End Set
    End Property

    ''' <summary>
    ''' Enable advanced mode. This feature is not ready yet
    ''' </summary>
    ''' <value>Boolean</value>
    ''' <returns>AdvancedMode</returns>
    ''' <remarks></remarks>
    <Browsable(False)> _
    Public Property AdvancedMode() As Boolean
        Get
            Return _AdvancedMode
        End Get
        Set(ByVal value As Boolean)
            _AdvancedMode = value
        End Set
    End Property

    ''' <summary>
    ''' Enable fade focus effect.
    ''' </summary>
    ''' <value>Boolean</value>
    ''' <returns>Current fadefocus effect</returns>
    ''' <remarks></remarks>
    <Description("Enable fade focus effect"), Category("Features"), _
    DefaultValue(True)> _
    Public Property EnableFadeFocus() As Boolean
        Get
            Return _FadeFocus
        End Get
        Set(ByVal value As Boolean)
            _FadeFocus = value
            Me.Refresh()
        End Set
    End Property

    ''' <summary>
    ''' Set clock time span against current time.
    ''' </summary>
    ''' <value>Timespan</value>
    ''' <returns>Current timespan</returns>
    ''' <remarks></remarks>
    <Description("Set clock time span against current time"), Category("Features"), _
    DefaultValue(GetType(TimeSpan), "00:00:00")> _
    Public Property ClockSpan() As TimeSpan
        Get
            Return _span
        End Get
        Set(ByVal value As TimeSpan)
            _span = value
        End Set
    End Property
#End Region

...
...

The one important thing to do is to lock the size of the control, since we will have fixed size from the background image. This can be done with override OnSizeChanged event.

Making the Stuff Work

After the initialize step, now we will continue with the main feature. Region Draw Surface Area contains a method that is needed for drawing Hour, Minute and Second hand. To create all the effects, we will have 3 timers for this control. The first timer will act as time indicator. It will tick each second and tell the control to update the second hand's position. The second timer will do the "bounce" effect. Now, if you observe the Windows gadget correctly, you will notice that after the second hand tick, it will bounce back a little, just like the real clock. In order to create that effect, we will need a timer to bounce the second hand after it ticks. The last timer will be used for the initialize process. After the control loads, it needs to find the position of the current time and place the hands in the right place in an elegant way. That's why we need to implement ISupportInitialize to do the work.

VB.NET
...

Public Sub EndInit() Implements System.ComponentModel.ISupportInitialize.EndInit
    'Activate the clock
    If Not Me.DesignMode Then
        If ((Now.Hour + _span.Hours) Mod 12) * _
		30 > (Now.Minute + _span.Minutes) * 6 Then
            _loadMod = 1
        Else
            _loadMod = 3
        End If
        _loadTimeframe = (Now.Minute + _span.Minutes) * 6 + (Now.Second \ 10)
        _loadHAngle = (((Now.Hour + _span.Hours) Mod 12) * _
	    30 + ((Now.Minute + _span.Minutes) * 6 \ 12)) / (_loadTimeframe / 6)
        _loadMAngle = 6

        _tmrLoad = New Timer
        _tmrLoad.Interval = 50
        _angleH = 0
        _angleM = 0
        _angleS = 0
        Me.Refresh()
        _tmrLoad.Start()
    End If
End Sub

...

Adding Designer Support

Designer Support

A good component should have some features that make development time easier. One of the features is designer support. This will making our component easier to use.

VB.NET
<System.Security.Permissions.PermissionSetAttribute_
	(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class TheClockDesigner
    Inherits System.Windows.Forms.Design.ControlDesigner

    Private lists As DesignerActionListCollection

    Public Overrides ReadOnly Property ActionLists() _
            As DesignerActionListCollection
        Get
            If lists Is Nothing Then
                lists = New DesignerActionListCollection()
                lists.Add( _
                New TheClockActionList(Me.Component))
            End If
            Return lists
        End Get
    End Property
End Class

Timezone Support

Timezone support

Use own time

For the last feature, we will add Timezone support for the component. By adding this feature, the user can choose what time she/he needs for the control instead of just following Windows standard control. This feature can also be integrated with designer support to make it even easier to use. Moreover, it will have some nice "Use My Own" feature that can adjust the clock time difference with input from the user.

Points of Interest

There's one problem still bothering me while creating this control. For a strange reason, I must adjust the second hand position determined by XP or Vista OS. I currently tested my project on those OSs, and find that I need to move the second hand 1 pixel to the left if I use XP OS. This code can be found in the DrawSecondHand method. Until now, it is still confusing for me to know that XP coordinate is slightly different from Vista coordinates.

VB.NET
Private Sub DrawSecondHand(ByVal g As Graphics, ByVal Angle As Single)
    Dim container As Rectangle
    If Angle <> 180 Then
        container = New Rectangle(-6, -64, 13, 129)
    Else
        If _ComInfo.OSVersion.StartsWith("5.1") Then
            container = New Rectangle(-7, -65, 13, 129) 'This works on XP
        Else
            container = New Rectangle(-6, -64, 13, 129) 'This works on Vista
        End If
    End If

    g.TranslateTransform(64.0F, 65.0F)
    g.RotateTransform(Angle)
    If _FadeFocus Then
        Using ia As New Imaging.ImageAttributes
            Dim cm As New Imaging.ColorMatrix

            cm.Matrix33 = _FadeAlpha
            ia.SetColorMatrix(cm)
            g.DrawImage(_imgS, container, 0, 0, 13, 129, _
                GraphicsUnit.Pixel, ia)
        End Using
    Else
        g.DrawImage(_imgS, container)
    End If
    'g.DrawImage(_imgS, container)
    g.ResetTransform()
End Sub

History

  • Version 1.0 - Initial release

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)