Analog Clock displayed over worksheet made purely with APIs !

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,621
Office Version
  1. 2016
Platform
  1. Windows
Hi all.

I've been experimenting with some GDI APIs to create an analog clock and this is what i've come up with so far. - Not exactly a very useful thing but was interesting to code and it is an alternative to the known clock made of an XY chart.

Tested on one PC only running WIN XP Excel 2003 SP3. I hope it works on different machines too.

See here a WorkBook Demo.

In a Standard Module :

Code:
Option Explicit
 
Private Type POINTAPI
    x As Long
    Y As Long
End Type
 
Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type
 
 Private Type LOGFONT
    lfHeight As Long
    lfWidth As Long
    lfEscapement As Long
    lfOrientation As Long
    lfWeight As Long
    lfItalic As String * 1
    lfUnderline As String * 1
    lfStrikeOut As String * 1
    lfCharSet As String * 1
    lfOutPrecision As String * 1
    lfClipPrecision As String * 1
    lfQuality As String * 1
    lfPitchAndFamily As String * 1
    lfFaceName As String * 32
End Type
 
 
Private Declare Function GetDC Lib "user32.dll" _
(ByVal hwnd As Long) As Long
 
Private Declare Function GetDeviceCaps Lib "gdi32.dll" _
(ByVal hdc As Long, _
ByVal nIndex As Long) As Long
  
Private Declare Function ReleaseDC Lib "user32" _
(ByVal hwnd As Long, _
ByVal hdc As Long) As Long
 
Private Declare Function SetPixel Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal Y As Long, _
ByVal crColor As Long) As Long
 
Private Declare Function InvalidateRect _
Lib "user32" (ByVal hwnd As Long, _
lpRect As Long, _
ByVal bErase As Long) As Long
 
Private Declare Function DrawText Lib "user32" _
Alias "DrawTextA" _
(ByVal hdc As Long, _
ByVal lpStr As String, _
ByVal nCount As Long, _
lpRect As RECT, _
ByVal wFormat As Long) As Long
 
Private Declare Function SetRect Lib "user32" _
(lpRect As RECT, _
ByVal X1 As Long, _
ByVal Y1 As Long, _
ByVal X2 As Long, _
ByVal Y2 As Long) As Long
 
Private Declare Function CreateFontIndirect Lib "gdi32" _
Alias "CreateFontIndirectA" _
(lpLogFont As LOGFONT) As Long
 
Private Declare Function SetBkMode Lib "gdi32" _
(ByVal hdc As Long, _
ByVal nBkMode As Long) As Long
 
Private Declare Function LineTo Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal Y As Long) As Long
 
Private Declare Function MoveToEx Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal Y As Long, _
lpPoint As POINTAPI) As Long
 
Private Declare Function SelectObject Lib "gdi32" _
(ByVal hdc As Long, _
ByVal hObject As Long) As Long
 
Private Declare Function CreatePen Lib "gdi32" _
(ByVal nPenStyle As Long, _
ByVal nWidth As Long, _
ByVal crColor As Long) As Long
 
Private Declare Function DeleteObject Lib "gdi32" _
(ByVal hObject As Long) As Long
 
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 RedrawWindow Lib "user32" _
(ByVal hwnd As Long, _
ByVal lprcUpdate As Long, _
ByVal hrgnUpdate As Long, _
ByVal fuRedraw As Long) As Long
 
Private Declare Function GetForegroundWindow Lib "user32" () As Long
 
Private Declare Function FindWindow Lib "user32.dll" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
 
Private Declare Function SetTimer Lib "user32.dll" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
 
Private Declare Function KillTimer Lib "user32.dll" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long) As Long

Private Const LOGPIXELSX        As Long = 88
Private Const LOGPIXELSY        As Long = 90
Private Const PointsPerInch     As Long = 72
Private Const PI                As Single = 3.14159265358979
Private Const Rads              As Single = PI / 180
 
Private Const RDW_INVALIDATE = &H1
Private Const RDW_ALLCHILDREN = &H80
 
Private Const PS_SOLID = 0
Private Const DT_CENTER = &H1
 
Private tp As POINTAPI
Private tR As RECT
Private oUpperLeftCell As Range
Private lTimerID As Long
Private X1 As Long, Y1 As Long
Private lDC  As Long
Private lRadius1 As Long, lRadius2 As Long
Private lRadius3 As Long, lRadius4 As Long
 
Sub Test()
    
    [COLOR=seagreen]'display the analog clock @ the
    'current screen location of cell 'D10' .
[/COLOR]    
    ShowClock TargetCell:=Range("D10")
 
End Sub

Sub StopClock()
 
    KillTimer 0, lTimerID
    lTimerID = 0
 
End Sub
 
Sub HideClock()
 
    InvalidateRect 0, 0, 0
    KillTimer 0, lTimerID
    lTimerID = 0
 
End Sub
 

[COLOR=seagreen]'=======================
'Main code              '
'=======================[/COLOR]
Private Sub ShowClock(TargetCell As Range)
 
    If lTimerID = 0 Then
        Set oUpperLeftCell = ActiveWindow.VisibleRange.Cells(1, 1)
        If Union(TargetCell, ActiveWindow.VisibleRange).Address _
        <> ActiveWindow.VisibleRange.Address Then
            GoTo errHandler:
        End If
        tp = GetRangeCenterPointInPixels(TargetCell)
        lTimerID = SetTimer(0, 0, 1000, AddressOf RunClock)
    End If
    Exit Sub
errHandler:
    MsgBox "Target Cell not visible.", vbCritical
 
End Sub
 
Private Sub RunClock()
                         
    On Error Resume Next
    
    If GetForegroundWindow = _
    FindWindow("XLMAIN", Application.Caption) Then
        If ActiveWindow.VisibleRange.Cells(1, 1).Address _
        <> oUpperLeftCell.Address Then
            InvalidateRect 0, 0, 0
            DoEvents
        End If
        Call CreateClock
        Call UpdateClock
    End If
 
End Sub
 
Private Sub CreateClock()
    
    Dim i As Long
    Dim lAngle As Long
    Dim X2 As Long, Y2 As Long
    Dim A2 As Long, B2 As Long
    
    X1 = tp.x
    Y1 = tp.Y
    
    lRadius1 = 60
    lRadius2 = lRadius1 * 80 / 100
    lDC = GetDC(0)
    SetBkMode lDC, 1
    Call CreateFont(lDC, True)
    For lAngle = 0 To 359
    
        For i = 7 To 10
        
            X2 = (lRadius1 + i) * (Sin(lAngle * Rads))
            Y2 = (lRadius1 + i) * (Cos(lAngle * Rads))
            SetPixel lDC, X2 + X1, Y2 + Y1, vbRed
        
        Next i
        
        X2 = (lRadius1) * (Sin(lAngle * Rads))
        Y2 = (lRadius1) * (Cos(lAngle * Rads))
        SetPixel lDC, X2 + X1, Y2 + Y1, vbRed
        A2 = lRadius2 * (Sin(lAngle * Rads))
        B2 = lRadius2 * (Cos(lAngle * Rads))
        SetRect tR, (A2 + X1) - 5, B2 + Y1 - 5, _
        A2 + X1 + 5, B2 + Y1 + 5
        Select Case lAngle
    
            Case Is = 0
                DrawText lDC, "6", 1, tR, DT_CENTER
            Case Is = 30
                DrawText lDC, "5", 1, tR, DT_CENTER
            Case Is = 60
                DrawText lDC, "4", 1, tR, DT_CENTER
            Case Is = 90
                DrawText lDC, "3", 1, tR, DT_CENTER
            Case Is = 120
                DrawText lDC, "2", 1, tR, DT_CENTER
            Case Is = 150
                DrawText lDC, "1", 1, tR, DT_CENTER
            Case Is = 180
                DrawText lDC, "12", 2, tR, DT_CENTER
            Case Is = 210
                DrawText lDC, "11", 2, tR, DT_CENTER
            Case Is = 240
                DrawText lDC, "10", 2, tR, DT_CENTER
            Case Is = 270
                DrawText lDC, "9", 1, tR, DT_CENTER
            Case Is = 300
                DrawText lDC, "8", 1, tR, DT_CENTER
            Case Is = 330
                DrawText lDC, "7", 1, tR, DT_CENTER
        
        End Select
    
    Next lAngle

End Sub

Private Sub UpdateClock()
 
    Dim tPt As POINTAPI
    Dim lhRng As Long
    Dim lhRPen As Long
    Dim lSecond As Single
    Dim lMinute As Single
    Dim lHour As Single
    
    lRadius3 = lRadius1 * 70 / 100
    lRadius4 = lRadius1 * 80 / 100
    
    
    lhRng = CreateEllipticRgn _
    (X1 - lRadius3, Y1 - lRadius3, X1 + lRadius3, Y1 + lRadius3)
    
    RedrawWindow 0, 0, lhRng, RDW_INVALIDATE + RDW_ALLCHILDREN
    
    DoEvents
    
    [COLOR=seagreen]'Seconds.
[/COLOR]    MoveToEx lDC, X1, Y1, tPt
    lhRPen = CreatePen(PS_SOLID, 1, vbRed)
    
    DeleteObject SelectObject(lDC, lhRPen)
    
    lSecond = Second(Time) * (2 * PI / 60)
    LineTo lDC, X1 + ((lRadius3) * 0.85 * Sin(lSecond)), _
    Y1 - ((lRadius3) * 0.85 * Cos(lSecond))
    
    [COLOR=seagreen]'Minutes.
[/COLOR]    MoveToEx lDC, X1, Y1, tPt
    
    lhRPen = CreatePen(PS_SOLID, 2, vbRed)
    
    DeleteObject SelectObject(lDC, lhRPen)
    
    lMinute = (Minute(Time) + (Second(Time) / 60)) * (2 * PI / 60)
    LineTo lDC, X1 + (lRadius3) * Sin(lMinute) * 0.8, _
    Y1 - (lRadius3) * Cos(lMinute) * 0.8
    
    [COLOR=seagreen]'Hours.[/COLOR]
    MoveToEx lDC, X1, Y1, tPt
    
    lhRPen = CreatePen(PS_SOLID, 4, vbBlack)
    
    DeleteObject SelectObject(lDC, lhRPen)
    
    lHour = (Hour(Time) + (Minute(Time) / 60)) * (2 * PI / 12)
    LineTo lDC, X1 + (lRadius4) * Sin(lHour) * 0.5, _
    Y1 - (lRadius4) * Cos(lHour) * 0.5
    
    Set oUpperLeftCell = ActiveWindow.VisibleRange.Cells(1, 1)
    
    ReleaseDC 0, lDC
 
End Sub
 
Private Function GetRangeCenterPointInPixels(rng As Range) As POINTAPI
 
    Dim CenterX, CenterY As Double
    Dim lDC As Long
 
    On Error Resume Next
    lDC = GetDC(0)
    CenterX = rng.Left + (rng.Width / 2)
    CenterY = rng.Top + (rng.Height / 2)
 
    With GetRangeCenterPointInPixels
        .x = ActiveWindow.PointsToScreenPixelsX((CenterX) * _
        (GetDeviceCaps _
        (lDC, LOGPIXELSX) / PointsPerInch * ActiveWindow.Zoom / 100))
        .Y = ActiveWindow.PointsToScreenPixelsY((CenterY) * _
        (GetDeviceCaps _
        (lDC, LOGPIXELSY) / PointsPerInch * ActiveWindow.Zoom / 100))
    End With
 
    ReleaseDC 0, lDC
 
End Function

Private Sub CreateFont(DC As Long, Optional Bold As Boolean)
 
    Dim uFont As LOGFONT
    Dim lNewFont As Long
 
    With uFont
        .lfFaceName = "Tahoma" & Chr$(0)
        .lfHeight = 12
        .lfWidth = 5
        .lfWeight = IIf(Bold, 900, 100)
        
    End With
 
    lNewFont = CreateFontIndirect(uFont)
    DeleteObject (SelectObject(DC, lNewFont))
 
End Sub

As a variation to the above, I guess placing the clock inside a modeless userform would be more practical.

Regards.
 

Excel Facts

Easy bullets in Excel
If you have a numeric keypad, press Alt+7 on numeric keypad to type a bullet in Excel.
Hi. back to this with the final update. .. It has taken me some quite amount of investigation and inquiry on how to properly combine GDI+ calls with the UpdateLayeredWindow api in order to achieve the two following main objectifs (special thanks to wqueto at VBForums for introducing me to the handy GdipCreateBitmapFromScan0 GDI+ function which optionally preserves the image alpha channel):

Achieved Objectifs:
1- Produce smooth-edged clock as well as smoother clcok composants.
2- Prevent the clock flicker that was observed when moving it off screen. (With the UpdateLayeredWindow, WM_PAINT is suppressed which is exactly what we need here.)

WORKBOOK DEMO

In addition to achieving the two above objectifs, the clock in this update has also the following features:
1- It Is minimizeable and adds a custom icon to the windows taskbar.
2- Has a right-click context menu to toggle the clock properties.
3- The ticking WAV sound can be toggled.
4- The running can be stopped-resumed.
5- The opacity can be changed.
6- It can be made AlwaysOnTop.






I won't post the entire vbaproject code here as it is too large (due to the raw bytes of the icon and wav sound resources).
Please, check out the code in the file demo.
 
Upvote 2
Hi

I checked it on my current laptop - Microsoft 365 MSO (Version 2212 Build 16.0.15928.20196) 32-bit. I can confirm that (1) it works on 32bit (it's great!), and (2) I clock's ticktock sound works (and is nicely in sync with the second-hand movement!). I've also tried the various settings, and haven't encountered any problems.

I looked at the original version to see what you changed, and to learn what I can from it. Doubtless, a lot of it is a reflection of what you've learnt over the decade+ since the original version. I've been trying to come to grips with GDI/GDI+ APIs, and have trying to make my own PictureBox class (that mirrors the VB6 control), so my questions/observations below are asked/posed in the (admittedly, very selfish) context of - "how can improve my own project?" :) Your thoughts on any of the following is (as always) greatly appreciated:
  • GetWindowDC (original) v GetDC - I had no idea GetWindowDC existed - is there a difference between the two? I assumed not, but I was just wondering and just wanted to check.
  • GDI+ - One big difference is that the new version now uses GDI+. Your graphics code is always useful guide for me on how to use GDI/GDI+ APIs without causing memory leaks (which I struggled wrapping my head around last year) and invariably crashing Excel! I'm wondering what prompted you to use GDI+ now, and - on a related point - why use CreateEllipticRgn and not the GDI or GDI+ equivalent? I wonder whether it might have something to do with it being a region / hittest?'
  • Dummy Parameters - You've used dummy parameters in the procedure declaration signatures - I've seen this before a few times by others, and I've always been curious as to why it's been written this way.
  • ReleaseDC - In the original, I noticed that you GetWindowDC/ReleaseDC on each run of the clock, but not in the new version, where it is released when the window is closed down. The latter approach is what I've used for the PictureBox class. I've only just realised that initialize/terminate the GDI+, though, at the start and end of every drawing procedure - lines, rectangles, etc - and I'm wondering now if I don't need to, and like here, I can termiante the GDI+ at the termination of the class along with the ReleaseDC, etc.
  • Sound - I had come across The Trick's method of tricking the PlaySound API to play MP3 files as WAV files on the VBForum's site, and it works nicely (especially to dramatically reduce the overall file size!), but it has a problem with really small MP3 files - like 1 second sound effects. Doubtless, your code above will solve that issue for me - thank you!
  • AutoRedraw - Obviously, I saw your comment in the new code, and this has been the principal challenge I've had to overcome for the Picturebox Class - I think I've managed to solve it thanks to your code solutions and a project on VbAccelarator, but I'll find out soon enough once I upload it onto github!
Thank you (again!).
Hi Dan,

* GetwindowDc retrieves the device context for the entire window, including title bar, menus, and scroll bars. Whereas GetDC is only for the client area ... For more flexibility and more control over the drawing regions, you can use GetDCEx

* I used GDI+ because I wanted anti-aliasing to smooth out the moving Hour, Min and Sec clock hands. Notice how I have used Call GdipSetSmoothingMode(hGraphics, SmoothingModeAntiAlias) and then calling GdipDrawLine when updating the clock. GDI doesn't support antialiasing so using it instead of using GDIPlus would have resulted in jagged and slightly pixelated lines.

* Using Optional Dummy parameters is just a habit I got myself in so as to make the SUB invisible to the GUI and therefore avoid unnecessary clutter within the list of available macros. This has a similar effect to using Option Private Module but works on individual SUBs instead of the entire subs within the module.

* I decided to declare the hDC variable at the module level and release the DC upon closing the form because the DC drawing operations take place in two different routines... As long as the DC is released when no longer needed it is ok. Same applies to all other GDI\GDIPlus objects.

* I remember downloading this clock tick sound file from some website a few years back (don't remember which website). I then edited the file to reduce its duration to about 2 secs which seemed to work ok. For portability, I extracted the sound bytes; placed the bytes in an array and passed the byte array to the sndPlaySoundW api with the SND_MEMORY flag.

* AutoRedraw not being supported in ms forms makes it difficult to draw persisting graphics. I have successfully used subclassing techniques in other projects to handle the painting in order to achieve the same AutoRedraw effect but I would rather avoid subclassing whenever possible (specially when using modeless forms) . The timer workaround hack I have used here (MonitorClockScreenPos Callback) is not perfect but I think, is good enough.

The next challenge is how to make the container userform invisible and display only the round clock floating on the screen as shown in the below picture.
This can easily be achieved by creating a GDI32 elliptic region and then using the SetWindowRgn api function BUT as you can see from the resulting image below ,the clock edges look jagged. so not good. The only way I know of that should produce irregular images with smoother egdes is the UpdateLayeredWindow api function but requires the image to have an alpha channel. I am still investigating including the GDIPlus library... Setting the GDIPlus smoothing mode doesn't seem to work with regions. Maybe I should be looking at the PNG format which supports transparency.



jagged.png
 
Last edited:
Upvote 1
Hi Jaafar.

That's very neat :)

I confirm that it works with Excel 2007 S2 on Win XP SP2.
 
Upvote 0
That's a really nice piece of coding. Mucho kudos. I'm on WIN XP Excel 2003 SP too; works fine.

Tell me, where's the best place to look to get accurate API call declarations?
 
Upvote 0
Thanks Peter and James for testing the code and i am glad it worked on other systems too.

Tell me, where's the best place to look to get accurate API call declarations?

I use the website allapai.net and the MSDN documentation plus the api viewer that you can google and download for free rom the net.

regards.
 
Upvote 0
Re: Analog Clock displayed over worksheet made purely with APIs ! ( UPDATE )

Hi all.

Here is an improvement on the previous Clock. This new clock is in fact a modeless userform in disguise.

The advantage it has over the previous one is that it doesn't have to be redrawn each time a worksheet is scrolled or a workbook window is moved hence no flickering takes place. - Also,the Clock can now be dragged around the screen with the mouse plus the background can be given a colour among other things.

WorkBook Example.

Proceedings:

1- Place this in a Standard module :

Code:
Option Explicit
 
Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type
 
Private Type POINTAPI
    x As Long
    y As Long
End Type
 
 Private Type LOGFONT
    lfHeight As Long
    lfWidth As Long
    lfEscapement As Long
    lfOrientation As Long
    lfWeight As Long
    lfItalic As String * 1
    lfUnderline As String * 1
    lfStrikeOut As String * 1
    lfCharSet As String * 1
    lfOutPrecision As String * 1
    lfClipPrecision As String * 1
    lfQuality As String * 1
    lfPitchAndFamily As String * 1
    lfFaceName As String * 32
End Type
 
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
 
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 Long) As Long
 
Private Declare Function GetWindowLong Lib "user32" _
Alias "GetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long) As Long
 
Private Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
 
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hwnd As Long) As Long
 
Private Declare Function DrawText Lib "user32" _
Alias "DrawTextA" _
(ByVal hdc As Long, _
ByVal lpStr As String, _
ByVal nCount As Long, _
lpRect As RECT, _
ByVal wFormat As Long) As Long
 
Private Declare Function CreateFontIndirect Lib "gdi32" _
Alias "CreateFontIndirectA" _
(lpLogFont As LOGFONT) As Long
 
Private Declare Function SetBkMode Lib "gdi32" _
(ByVal hdc As Long, _
ByVal nBkMode As Long) As Long
 
Private Declare Function SelectObject Lib "gdi32" _
(ByVal hdc As Long, _
ByVal hObject As Long) As Long
 
Private Declare Function CreatePen Lib "gdi32" _
(ByVal nPenStyle As Long, _
ByVal nWidth As Long, _
ByVal crColor As Long) As Long
 
Private Declare Function DeleteObject Lib "gdi32" _
(ByVal hObject As Long) As Long
 
Private Declare Function SetPixel Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal crColor As Long) As Long
 
Private Declare Function SetRect Lib "user32" _
(lpRect As RECT, _
ByVal X1 As Long, _
ByVal Y1 As Long, _
ByVal X2 As Long, _
ByVal Y2 As Long) As Long
 
Private Declare Function GetWindowDC Lib "user32.dll" _
(ByVal hwnd As Long) As Long
 
Private Declare Function ReleaseDC Lib "user32" _
(ByVal hwnd As Long, _
ByVal hdc As Long) 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 Declare Sub ReleaseCapture Lib "user32" ()
 
Private Declare Function RedrawWindow Lib "user32" _
(ByVal hwnd As Long, _
ByVal lprcUpdate As Long, _
ByVal hrgnUpdate As Long, _
ByVal fuRedraw As Long) As Long
 
Private Declare Function LineTo Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal y As Long) As Long
 
Private Declare Function MoveToEx Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, _
ByVal y As Long, _
lpPoint As POINTAPI) As Long
 
Private Declare Function SetTimer Lib "user32.dll" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
 
Private Declare Function KillTimer Lib "user32.dll" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long) As Long
 
Private Declare Sub Sleep Lib "kernel32.dll" _
(ByVal dwMilliseconds As Long)
 
Private Const RDW_INVALIDATE As Long = &H1
Private Const RDW_ALLCHILDREN As Long = &H80
Private Const PS_SOLID As Long = 0
Private Const DT_CENTER As Long = &H1
 
Private Const GWL_STYLE As Long = (-16)
Private Const WS_CAPTION As Long = &HC00000
 
Private Const WM_NCLBUTTONDOWN As Long = &HA1
Private Const HTCAPTION As Long = 2
 
Private Const PI  As Single = 3.14159265358979
Private Const Rads  As Single = PI / 180
 
Private tR As RECT
 
Private ofrm As UserForm
Private objCmb As CommandBar
 
Private lfrmHwnd As Long
Private lDC As Long
Private lTimerID As Long
Private lRadius1 As Long
Private lRadius2 As Long
Private lRadius3 As Long
Private lRadius4 As Long
Private X1 As Long
Private Y1 As Long
 
Private Sub RunClock()
 
    Call CreateClock
    Call UpdateClock
 
End Sub
 
Private Sub UpdateClock()
 
    Dim tPt As POINTAPI
    Dim lhRng As Long
    Dim lhRPen As Long
    Dim lSecond As Single
    Dim lMinute As Single
    Dim lHour As Single
 
 
    lRadius3 = lRadius1 * 70 / 100
    lRadius4 = lRadius1 * 85 / 100
 
 
    lhRng = CreateEllipticRgn _
    (X1 - lRadius3, Y1 - lRadius3, X1 + lRadius3, Y1 + lRadius3)
 
    RedrawWindow lfrmHwnd, 0, lhRng, RDW_INVALIDATE
 
    DoEvents
 
    'Seconds.
    MoveToEx lDC, X1, Y1, tPt
    lhRPen = CreatePen(PS_SOLID, 1, vbRed)
 
    DeleteObject SelectObject(lDC, lhRPen)
 
    lSecond = Second(time) * (2 * PI / 60)
    LineTo lDC, X1 + ((lRadius3) * 0.85 * Sin(lSecond)), _
    Y1 - ((lRadius3) * 0.85 * Cos(lSecond))
 
    'Minutes.
    MoveToEx lDC, X1, Y1, tPt
 
    lhRPen = CreatePen(PS_SOLID, 3, vbBlack)
 
    DeleteObject SelectObject(lDC, lhRPen)
 
    lMinute = (Minute(time) + (Second(time) / 60)) * (2 * PI / 60)
    LineTo lDC, X1 + (lRadius3) * Sin(lMinute) * 0.8, _
    Y1 - (lRadius3) * Cos(lMinute) * 0.8
 
    'Hours.
    MoveToEx lDC, X1, Y1, tPt
 
    lhRPen = CreatePen(PS_SOLID, 4, vbBlack)
 
    DeleteObject SelectObject(lDC, lhRPen)
 
    lHour = (Hour(time) + (Minute(time) / 60)) * (2 * PI / 12)
    LineTo lDC, X1 + (lRadius4) * Sin(lHour) * 0.5, _
    Y1 - (lRadius4) * Cos(lHour) * 0.5
    ReleaseDC 0, lDC
 
End Sub
 
Private Sub CreateClock()
 
    Dim i As Long
    Dim lAngle As Long
    Dim X2 As Long, Y2 As Long
    Dim A2 As Long, B2 As Long
 
    Dim p As POINTAPI
    Dim rct As RECT
 
    lDC = GetWindowDC(lfrmHwnd)
 
 
    X1 = (UserForm1.Width / 2)
    Y1 = (UserForm1.Height / 2)
 
 
    lRadius1 = (UserForm1.Width / 2) - 13
    lRadius2 = lRadius1 * 80 / 100
 
 
    SetBkMode lDC, 1
    Call CreateFont(lDC, True)
 
    For lAngle = 0 To 359
 
        For i = 6 To 10
 
            X2 = (lRadius1 + i) * (Sin(lAngle * Rads))
            Y2 = (lRadius1 + i) * (Cos(lAngle * Rads))
            SetPixel lDC, X2 + X1, Y2 + Y1, vbBlack
 
        Next i
 
        X2 = (lRadius1) * (Sin(lAngle * Rads))
        Y2 = (lRadius1) * (Cos(lAngle * Rads))
        SetPixel lDC, X2 + X1, Y2 + Y1, vbBlack
 
 
        A2 = lRadius2 * (Sin(lAngle * Rads))
        B2 = lRadius2 * (Cos(lAngle * Rads))
 
        SetRect tR, (A2 + X1) - 5, B2 + Y1 - 5, _
        A2 + X1 + 5, B2 + Y1 + 5
 
        Select Case lAngle
 
            Case Is = 0
                DrawText lDC, "6", 1, tR, DT_CENTER
            Case Is = 30
                DrawText lDC, "5", 1, tR, DT_CENTER
            Case Is = 60
                DrawText lDC, "4", 1, tR, DT_CENTER
            Case Is = 90
                DrawText lDC, "3", 1, tR, DT_CENTER
            Case Is = 120
                DrawText lDC, "2", 1, tR, DT_CENTER
            Case Is = 150
                DrawText lDC, "1", 1, tR, DT_CENTER
            Case Is = 180
                DrawText lDC, "12", 2, tR, DT_CENTER
            Case Is = 210
                DrawText lDC, "11", 2, tR, DT_CENTER
            Case Is = 240
                DrawText lDC, "10", 2, tR, DT_CENTER
            Case Is = 270
                DrawText lDC, "9", 1, tR, DT_CENTER
            Case Is = 300
                DrawText lDC, "8", 1, tR, DT_CENTER
            Case Is = 330
                DrawText lDC, "7", 1, tR, DT_CENTER
 
        End Select
 
    Next lAngle
 
End Sub
 
Private Sub CreateFont(DC As Long, Optional Bold As Boolean)
 
    Dim uFont As LOGFONT
    Dim lNewFont As Long
 
    With uFont
        .lfFaceName = "Tahoma" & Chr$(0)
        .lfHeight = 12
        .lfWidth = 5
        .lfWeight = IIf(Bold, 900, 100)
    End With
 
    lNewFont = CreateFontIndirect(uFont)
    DeleteObject (SelectObject(DC, lNewFont))
 
End Sub
 
Sub FormSetUp(frm As UserForm1)
 
    Dim lHr As Long, IOldStyle As Long, INewStyle As Long
    Dim IOldExStyle As Long
 
    Set ofrm = frm
   lfrmHwnd = FindWindow(vbNullString, frm.Caption)
   frm.BackColor = RGB(200, 250, 150)
    frm.Width = 150
    frm.Height = 150
'
    'Create rightclick close menu
    On Error Resume Next
    CommandBars("GlassPopUp").Delete
    Set objCmb = Application.CommandBars.Add(Position:=msoBarPopup)
    With objCmb
        objCmb.Name = "GlassPopUp"
        With .Controls.Add(msoControlButton)
            .Caption = "CloseMe"
            .OnAction = "CloseClock"
        End With
    End With
    On Error GoTo 0
 
    frm.MousePointer = fmMousePointerSizeAll
    IOldStyle = GetWindowLong(lfrmHwnd, GWL_STYLE)
    INewStyle = IOldStyle And Not WS_CAPTION
    SetWindowLong lfrmHwnd, GWL_STYLE, INewStyle
    DrawMenuBar lfrmHwnd
    lHr = CreateEllipticRgn(0, 0, frm.Width, frm.Height)
    SetWindowRgn lfrmHwnd, lHr, True
 
    frm.Hide
    lTimerID = SetTimer(0, 0, 1000, AddressOf RunClock)
    Sleep 1000
    frm.Show vbModeless
 
End Sub
 
Sub RlsCapture()
 
        Call ReleaseCapture
        SendMessage lfrmHwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0
 
End Sub
 
Sub ShowPopUp()
 
    objCmb.ShowPopUp
 
End Sub
 
Sub CloseClock()
 
    KillTimer 0, lTimerID
    Unload ofrm
 
End Sub


2- Add a UserForm to the project and place this code in its module :
(leave its default name UserForm1)

Code:
Option Explicit
 
Private Sub UserForm_Initialize()
 
    Call FormSetUp(Me)
 
End Sub
 
Private Sub UserForm_MouseDown _
(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal x As Single, ByVal y As Single)
 
    If Button = 2 Then Call ShowPopUp
 
End Sub
 
Private Sub UserForm_MouseMove _
(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal x As Single, ByVal y As Single)
 
    'move the captionless form with the mouse
     If Button = 1 Then
        Call RlsCapture
    End If
 
End Sub


3- ... and here is how to call the AnalogClock :


Code:
Option Explicit
 
Sub ShowClock()
 
    UserForm1.Show vbModeless
 
End Sub

Regards.
 
Upvote 0
Hi Jafaar

using XL2007 on Win7 Ultimate... Looks good :) Userform one looks especially great!
 
Upvote 0
Hi Jafaar

using XL2007 on Win7 Ultimate... Looks good :) Userform one looks especially great!

Thanks Jon for the compliment. I am re-uploading some of my workbook projects via Box.net .The workbook links were previously hosted by the annoying/slow and user unfriendly FileSharing.com file uploading site and have all long expired.
 
Upvote 0

Forum statistics

Threads
1,215,357
Messages
6,124,482
Members
449,165
Latest member
ChipDude83

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top