_Clock in a Cell with a Twist_ !!

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,596
Office Version
  1. 2016
Platform
  1. Windows
:eek: This code uses API functions.If you try it , please save your work first :eek:

Hi all,

How to show a clock in a worksheet Cell is a question frequently asked.

Although, one could use the Ontime Method or use an API timer to update the time in the Cell, unfortunatly, both technics have some main drawbacks such as the clock doesn't update if the worksheet is in edit mode or if the user is selecting a range . Also, the Undo functionality is lost.

Here, I have used a different approach ( or a trick I should say ) by creating a Static Window, adjusting some of its Styles, resizing it to fit an accomodating Cell and finally placing it over that Cell so it looks as if its the Cell itsef !

This is by no means a perfect solution but at least, it overcomes the two main problems mentioned above because now, the repeating code happens in a different window from XL.

When the size of the underlying Cell is changed the clock will adjust automatically to reflect the change. The same happens if the worksheet Zoom has changed or the worksheet is scrolled.

The only thing that I haven't managed to do is to automatically be able to change the Clock formatting if the underlying Cell's formatting changes. This needs some more difficult Subclassing work and I am not quite there yet.

Here is a download demo :http://www.savefile.com/files/4303471


Here is the Code behind this Cell Clock : ( Place it in a Standard Module and run the Test routine )


Code:
Option Explicit

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Private Declare Function SetTimer Lib "user32" _
(ByVal hwnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long

Private Declare Function KillTimer Lib "user32" _
(ByVal hwnd As Long, ByVal nIDEvent As Long) As Long

Private Declare Function SetWindowPos Lib "user32" _
(ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, _
ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
 
Private Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long
 
Private Declare Function GetDesktopWindow Lib "user32" () As Long

Private Declare Function GetActiveWindow Lib "user32" () As Long
 
Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String) As Long
 
Private Declare Function DestroyWindow Lib "user32" _
(ByVal hwnd As Long) As Long
 
Private Declare Function CreateWindowEx Lib "user32" _
   Alias "CreateWindowExA" (ByVal dwExStyle As Long, _
   ByVal lpClassName As String, ByVal lpWindowName _
   As String, ByVal dwStyle As Long, ByVal x As Long, _
   ByVal y As Long, ByVal nWidth As Long, _
   ByVal nHeight As Long, ByVal hWndParent As Long, _
ByVal hMenu As Long, ByVal hInstance As Long, lpParam As Any) As Long

Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
 
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
 
Private Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type

Private Const WS_EX_NOACTIVATE = &H8000000
Private Const GWL_EXSTYLE = (-20)
Private Const WS_EX_TOOLWINDOW = &H80&
Private Const WS_CHILD = &H40000000
Private Const SS_CENTER = &H1
Private Const SW_HIDE = &H0
Private Const SW_NORMAL = 1

Private tRngRect As RECT
Private tRngNewDims As RECT

Private lngTimerID As Long
Private hWndStatic As Long
Private lngPrevProc As Long
Private lngWndXL As Long


Private objTargetCell As Range

'_________________________________________________________________________________________
Private Sub Create_Clock(Cell As Range)
    
    '\ store the clock cell in a variable
    Set objTargetCell = Cell
    '\ get the clock cell dims in Pixels
    tRngRect = GetNewRangeRect(Cell)
    '\ get XL window handle
    lngWndXL = FindWindow("XLMAIN", Application.Caption)
    '\ create a Static window and adjust its Styles and position so
    '\ it looks as if it is a cell
     With tRngRect
      hWndStatic = CreateWindowEx(WS_EX_NOACTIVATE + WS_EX_TOOLWINDOW, "STATIC", _
            vbNullString, SS_CENTER + WS_CHILD, .Left + 1, .Top + 1, (.Right - .Left) - 2, _
            (.Bottom - .Top) - 2, GetDesktopWindow, 0, 0, 0)
       End With
      '\ if success, start the timer.
      If hWndStatic > 0 Then
        lngTimerID = SetTimer(0, 0, 1, AddressOf TimerCallBack)
      End If

End Sub

'____________________________________________________________________________________________
Private Sub TimerCallBack()

    On Error Resume Next
    Static tRngPrevDims As RECT
    Static lngPrev_VScroll As Long
    Static lngPrev_HScroll As Long
    Static lngPrev_Zoom As Long
    Dim strTime As String
    
    '\ show Clock only if XL is active
    If GetActiveWindow = lngWndXL And ActiveSheet Is objTargetCell.Parent Then
        ShowWindow hWndStatic, SW_NORMAL
        With tRngNewDims
            .Bottom = objTargetCell.Top + objTargetCell.Height
            .Left = objTargetCell.Left
            .Right = objTargetCell.Left + objTargetCell.Width
            .Top = objTargetCell.Top
        End With
        '\ if the clock cell has changed of location or size
        '\ then adjust its dimensions to fit the undelying cell
        If RectHasChanged(tRngPrevDims, tRngNewDims) Then
            UpdateClockWindow objTargetCell
        End If
        '\ strore oldold dims for next round comparison
        tRngPrevDims = tRngNewDims
        '\ check if the user has scrolled the sheet
        '\ if so, adjust the dims of the underlying cell
        If Application.ActiveWindow.ScrollRow <> lngPrev_VScroll Or _
        Application.ActiveWindow.ScrollColumn <> lngPrev_HScroll _
        Then
            UpdateClockWindow objTargetCell
        End If
        '\ stroe the old window scroll values for next round
        lngPrev_VScroll = Application.ActiveWindow.ScrollRow
        lngPrev_HScroll = Application.ActiveWindow.ScrollColumn
        '\ again, check for a zoom change
        '\ if the zoom has changed, adjust the underlying cell dims
        If Application.ActiveWindow.Zoom <> lngPrev_Zoom Then
            UpdateClockWindow objTargetCell
        End If
        '\ store new zoom for next time round
        lngPrev_Zoom = Application.ActiveWindow.Zoom
        strTime = Format(Now, "HH:MM:SS")
        SetWindowText hWndStatic, strTime
    Else
        '\ if a different app is active or a different sheet
        '\ hide the clock.
        ShowWindow hWndStatic, SW_HIDE
    End If

End Sub



' ***************************   Supporting Routines   ********************************

Private Function RectHasChanged(tOldRect As RECT, tNewRect As RECT) As Boolean

    With tOldRect
        RectHasChanged = (.Top <> tNewRect.Top Or .Left <> tNewRect.Left _
        Or .Right <> tNewRect.Right Or .Bottom <> tNewRect.Bottom)
    End With

End Function
'_________________________________________________________________________________________
Private Function GetNewRangeRect(rng As Range) As RECT

    Dim tWindowRect As RECT
    Dim lngXLDesk As Long
    Dim lngXLChart As Long
    Dim objChart As ChartObject

    Set objChart = ActiveSheet.ChartObjects.Add(0, 0, 0, 0)
    
    With objChart
        .Top = rng.Top
        .Left = rng.Left
        .Height = rng.Height
        .Width = rng.Width
        .Activate
        .Delete
    End With
    lngXLDesk = FindWindowEx(lngWndXL, 0&, "XLDESK", vbNullString)
    lngXLChart = FindWindowEx(lngXLDesk, 0&, "EXCELE", vbNullString)
    GetWindowRect lngXLChart, tWindowRect
   GetNewRangeRect = tWindowRect

 End Function
'________________________________________________________________________________
Private Sub RedimClock(tNewDim As RECT)

    With tNewDim
        SetWindowPos hWndStatic, 0, .Left + 1, .Top + 1, _
        (.Right - .Left) - 2, (.Bottom - .Top) - 2, 0
    End With

End Sub
'_________________________________________________________________________________
Private Sub UpdateClockWindow(rng As Range)
    '\ repositionne the clock cell
    tRngRect = GetNewRangeRect(rng)
    Call RedimClock(tRngRect)

End Sub

'_________________________________________________________________________________
Sub Destroy_Control()

    '\\CleanUp
    DestroyWindow hWndStatic
    KillTimer 0, lngTimerID
    Set objTargetCell = Nothing
    
End Sub

'_________________________________________________________________________________

'Run this procedure
Sub Test()

    If Not CBool(IsWindow(hWndStatic)) Then
        Call Create_Clock(Sheet1.Range("B10"))
    Else
        MsgBox "Clock already on display !", vbCritical
    End If

End Sub

Note : This won't work with XL97 as it uses the AddressOf operator.

Any feedback much appreciated.

Regards.
 

Excel Facts

Highlight Duplicates
Home, Conditional Formatting, Highlight Cells, Duplicate records, OK to add pink formatting to any duplicates in selected range.
Hi Jaafar

Seems to work OK on my system (Windows XP Home SP2, Excel 2003).

DominicB
 
Upvote 0
Jaafar

Doesn't work on 2000.:eek:

Fails here.
Code:
lngXLhWnd = Application.hwnd
 
Upvote 0
NICE, Jafaar!!
works for me too
fantastic idea


for your info:
you can run a clock on a hidden sheet and use a formula to display it on your activesheet
='hidden sheet'!$A$1

very simple
only drawback: in EDIT-mode no clock
UNDO still available :)

kind regards,
Erik
 
Upvote 0
Thanks everyone for the feedback.

Norie: Yes, I that is the offending line because the Hwnd Property of the Application Object wasn't introduced until XL2002. The same goes for the HInstance Property which is also in use in the code.

I have now edited the code and hopefully it should now work for XL2000 as well.

Regards.
 
Upvote 0
jaafar

Edited code works now.:)

Any chance you could indicate what you changed?

I think I know what but I'm not 100% sure.
 
Upvote 0
jaafar

Edited code works now.:)

Any chance you could indicate what you changed?

I think I know what but I'm not 100% sure.

I just used the FindWindow API to which I passed the XL Class name "XLMAIN" in order to retrieve the XL hwnd Property.

I then stored this handle in a Module level variable so I can use throughout the Code instead of using Application.Hwnd.

As for the HInstance Property, I just replaced with 0 which still works as an argument for the CreateWindowEx API.

Regards.
 
Upvote 0

Forum statistics

Threads
1,214,653
Messages
6,120,749
Members
448,989
Latest member
mariah3

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