CodeItBetter Programming Another VB Programming Blog

How to make an Analog Clock

Posted on January 4, 2009
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
'Miscellaneous - How to make an Analog Clock
Option Explicit
 
Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal x1 As Long, _
    ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long
Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, _
    ByVal hrgn As Long, ByVal bRedraw As Boolean) As Long
 
Private Declare Function ReleaseCapture Lib "user32" () As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
    (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const HTCAPTION = 2
Private Const WM_NCLBUTTONDOWN = &HA1
Private Const WM_SYSCOMMAND = &H112
 
Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type
Private Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" _
    (ByVal uAction As Long, ByVal uParam As Long, ByRef lpvParam As RECT, _
    ByVal fuWinIni As Long) As Long
Private Const SPI_GETWORKAREA = 48
 
Private Const PI = 3.14159265
Private Const GRAB_RADIUS = 3
 
Private Declare Function CreateFont Lib "gdi32" Alias "CreateFontA" (ByVal H As Long, _
    ByVal W As Long, ByVal E As Long, ByVal O As Long, ByVal W As Long, ByVal I As Long, _
    ByVal u As Long, ByVal S As Long, ByVal C As Long, ByVal OP As Long, ByVal CP As Long, _
    ByVal Q As Long, ByVal PAF As Long, ByVal F As String) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
 
' Put the form in the lower right corner.
Private Sub PositionForm()
    Dim wa_info As RECT
    Dim wa_wid As Single
    Dim wa_hgt As Single
 
    If SystemParametersInfo(SPI_GETWORKAREA, 0, wa_info, 0) <> 0 Then
        ' We got the work area bounds.
        ' Center the form in the work area.
        wa_wid = ScaleX(wa_info.Right, vbPixels, vbTwips)
        wa_hgt = ScaleY(wa_info.Bottom, vbPixels, vbTwips)
    Else
        ' We did not get the work area bounds.
        ' Center the form on the whole screen.
        wa_wid = Screen.Width
        wa_hgt = Screen.Height
    End If
 
    ' Position the form.
    Me.Move wa_wid - Width, wa_hgt - Height
End Sub
 
' Center the form
Private Sub CenterForm(ByVal frm As Form)
    Dim wa_info As RECT
    Dim wa_wid As Single
    Dim wa_hgt As Single
    Dim wa_left As Single
    Dim wa_top As Single
 
    If SystemParametersInfo(SPI_GETWORKAREA, 0, wa_info, 0) <> 0 Then
        ' We got the work area bounds.
        ' Center the form in the work area.
        wa_wid = ScaleX(wa_info.Right, vbPixels, vbTwips)
        wa_hgt = ScaleY(wa_info.Bottom, vbPixels, vbTwips)
        wa_left = ScaleX(wa_info.Left, vbPixels, vbTwips)
        wa_top = ScaleY(wa_info.Top, vbPixels, vbTwips)
    Else
        ' We did not get the work area bounds.
        ' Center the form on the whole screen.
        wa_wid = Screen.Width
        wa_hgt = Screen.Height
    End If
 
    ' Center the form.
    frm.Move (wa_wid - Width + wa_left) / 2, (wa_hgt - Height + wa_top) / 2
End Sub
 
' Draw the clock's face without hands.
Private Sub DrawFace()
    Dim hrgn As Long
    Dim xoff As Single
    Dim yoff As Single
    Dim cx As Single
    Dim cy As Single
    Dim theta As Single
    Dim dtheta As Single
    Dim I As Integer
    Dim x1 As Single
    Dim y1 As Single
    Dim x2 As Single
    Dim y2 As Single
    Dim txt As String
    Dim new_font As Long
    Dim old_font As Long
 
    ' ControlBox = False
    ' ShowInTaskbar = False
    Caption = ""
    BorderStyle = vbBSNone
    AutoRedraw = True
    ScaleMode = vbPixels
 
    ' Make an elliptical region centered
    ' over the drawing area.
    xoff = (ScaleX(Width, vbTwips, vbPixels) - ScaleWidth) / 2
    yoff = (ScaleY(Height, vbTwips, vbPixels) - ScaleHeight) / 2
    hrgn = CreateEllipticRgn(xoff, yoff, xoff + ScaleWidth + 1, yoff + ScaleHeight + 1)
    SetWindowRgn hwnd, hrgn, False
 
    ' Draw the clock face.
    cx = (ScaleWidth - 1) / 2
    cy = (ScaleHeight - 1) / 2
    FillStyle = vbFSSolid
    Circle (cx, cy), GRAB_RADIUS
    FillStyle = vbFSTransparent
 
    ' Draw the tic marks and numerals.
    dtheta = PI / 30
    theta = -10 * dtheta
    For I = 0 To 59
        ' Draw the tic marks.
        x1 = cx + cx * Cos(theta)
        y1 = cy + cy * Sin(theta)
        If I Mod 5 = 0 Then
            ' Label the digit.
            txt = Format$(I \ 5 + 1)
 
            ' Create a rotated font.
            new_font = CustomFont(16, 0, (3 * PI / 2 - theta) * 1800 / PI, 0, 700, False, _
                False, False, "Times New Roman")
            old_font = SelectObject(hdc, new_font)
 
            ' Draw the text.
            ' Find the point lined up along the tic mark.
            x2 = cx + (cx - 1) * Cos(theta) * 0.95
            y2 = cy + (cy - 1) * Sin(theta) * 0.95
 
            ' Offset by distance TextWidth/2 rotated
            ' so the text is centered.
            CurrentX = x2 + TextWidth(txt) / 2 * Sin(theta)
            CurrentY = y2 - TextWidth(txt) / 2 * Cos(theta)
            ForeColor = RGB(0, 0, 128)
            Print txt
            ForeColor = vbBlack
 
            ' Restore the original font and delete the
            ' new font to free resources.
            SelectObject hdc, old_font
            DeleteObject new_font
 
            x2 = cx + cx * Cos(theta) * 0.9
            y2 = cy + cy * Sin(theta) * 0.9
        Else
            x2 = cx + cx * Cos(theta) * 0.95
            y2 = cy + cy * Sin(theta) * 0.95
        End If
        Line (x1, y1)-(x2, y2)
        theta = theta + dtheta
    Next I
 
    ' Make the image permanent.
    Picture = Image
End Sub
 
' Make a customized font and return its handle.
Private Function CustomFont(ByVal hgt As Long, ByVal wid As Long, ByVal escapement As Long, _
    ByVal orientation As Long, ByVal wgt As Long, ByVal is_italic As Long, _
    ByVal is_underscored As Long, ByVal is_striken_out As Long, ByVal face As String) As Long
    Const CLIP_LH_ANGLES = 16   ' Needed for tilted fonts.

    CustomFont = CreateFont(hgt, wid, escapement, orientation, wgt, is_italic, is_underscored, _
        is_striken_out, 0, 0, CLIP_LH_ANGLES, 0, 0, face)
End Function
 
' Draw the clock's hands.
Private Sub DrawHands()
    Const HOUR_R = 0.3
    Const MIN_R = 0.5
    Const SEC_R = 0.75
 
    Dim cx As Single
    Dim cy As Single
    Dim theta As Single
    Dim x2 As Single
    Dim y2 As Single
    Dim time_now As Date
 
    ' Draw the clock face.
    cx = (ScaleWidth - 1) / 2
    cy = (ScaleHeight - 1) / 2
 
    ' Clear the previous hands.
    Cls
 
    ' Draw the hour hand.
    time_now = Time
    theta = -PI / 2 + 4 * PI * (CSng(time_now))
    x2 = cx + cx * Cos(theta) * HOUR_R
    y2 = cy + cy * Sin(theta) * HOUR_R
    DrawWidth = 3
    Line (cx, cy)-(x2, y2)
 
    ' Draw the minute hand.
    theta = -PI / 2 + PI / 30 * Minute(time_now)
    x2 = cx + cx * Cos(theta) * MIN_R
    y2 = cy + cy * Sin(theta) * MIN_R
    DrawWidth = 2
    Line (cx, cy)-(x2, y2)
 
    ' Draw the second hand.
    theta = -PI / 2 + PI / 30 * Second(time_now)
    x2 = cx + cx * Cos(theta) * SEC_R
    y2 = cy + cy * Sin(theta) * SEC_R
    DrawWidth = 1
    Line (cx, cy)-(x2, y2)
End Sub
 
' Return True if the mouse is near the center of the form.
Private Function OverCenter(ByVal X As Single, ByVal Y As Single) As Boolean
    Dim cx As Single
    Dim cy As Single
    Dim dx As Single
    Dim dy As Single
 
    ' See if the point is close enough to the center.
    cx = (ScaleWidth - 1) / 2
    cy = (ScaleHeight - 1) / 2
    dx = cx - X
    dy = cy - Y
    OverCenter = (dx * dx + dy * dy <= GRAB_RADIUS * GRAB_RADIUS)
End Function
 
Private Sub Form_Load()
    ' Draw the face without the hands.
    DrawFace
    ' Put the form in the lower right corner.
    PositionForm
End Sub
 
' If the mouse is at the center of the clock, let the user move it.
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If OverCenter(X, Y) Then
        ' Move the form.
        ReleaseCapture
        SendMessage hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&
    ElseIf Button = vbRightButton Then
        ' Display the popup.
        PopupMenu mnuPopup
    End If
End Sub
 
Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If OverCenter(X, Y) Then
        If MousePointer <> vbCrosshair Then MousePointer = vbCrosshair
    Else
        If MousePointer <> vbDefault Then MousePointer = vbDefault
    End If
End Sub
 
Private Sub mnuPopupExit_Click()
    Unload Me
End Sub
 
' Draw the clock's hands.
Private Sub tmrTick_Timer()
    DrawHands
End Sub
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment


 

No trackbacks yet.