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.
#Region "The Clock - Copyright (C) 2007 Michael Rawi"
#End Region
Imports System.DateTime
<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.
...
...
#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
Out = 1
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.
...
...
#Region "Properties"
<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
<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
<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
<Browsable(False)> _
Public Property AdvancedMode() As Boolean
Get
Return _AdvancedMode
End Get
Set(ByVal value As Boolean)
_AdvancedMode = value
End Set
End Property
<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
<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.
...
Public Sub EndInit() Implements System.ComponentModel.ISupportInitialize.EndInit
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
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.
<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
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.
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)
Else
container = New Rectangle(-6, -64, 13, 129)
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.ResetTransform()
End Sub
History
- Version 1.0 - Initial release