Click here to Skip to main content
11,795,568 members (79,709 online)
Click here to Skip to main content

XCPClock - CodeProject Clock

, 27 Aug 2008 CPOL 97.1K 2.1K 133
Rate this:
Please Sign up or sign in to vote.
XCPClock displays CodeProject clock with four skins.


After finishing my XAnalogClock article, I wanted free-floating clock to use on my desktop. XCPClock is implemented using SetLayeredWindowAttributes, which is available in Win 2000 and later.


The right-click menu allows you to set transparency, always on top, second hand, and choose a skin:



Here are the skins available:

screenshot screenshot screenshot screenshot screenshot
screenshot screenshot screenshot screenshot screenshot
screenshot screenshot screenshot screenshot screenshot
screenshot screenshot screenshot screenshot screenshot


Transparency setting allows you to make underlying window partially visible:

When transparency setting is not fully opaque (less than 255), left-clicks and left-double-clicks will be passed on to underlying window (click-thru). However, even when transparency is enabled (less than 255), you can still use right-click to bring up popup menu, and you can still move clock by dragging (holding down left mouse button while moving mouse).

The Transparent Silver Ring and Transparent Minimal skins offer another transparency option: the clock face itself is completely transparent (except for center pivot, date box, and (for Transparent Silver Ring) the outer ring), so you can click on any window beneath the XCPClock. For these two skins, the minute- and hour-hand color changes automatically, depending on color of underlying window, so as to maintain good visibility (thanks to John Simmons' excellent article Determining Ideal Text Color Based on Specified Background Color).

The Transparency Dialog gives some tips on setting the transparency:


Command Line Options

Sometimes just one clock isn't enough. I often deal with people in different time zones, and it's handy to be able to look at clock that shows time at their location:


To display the above "clock rack," I use this cmd file:

start XCPClock.exe -s crystalgreen -h -3 -t Honolulu -x 400 -y 300 -w 1
start XCPClock.exe -s crystalblue -t "Los Angeles" -x 501 -y 300 -w 1
start XCPClock.exe -s cpbob -h +3 -t Toronto -x 602 -y 300 -w 1
start XCPClock.exe -s crystalblack -h +9 -t Berlin -x 703 -y 300 -w 1

Here are available command line options:

Option Example Description
-h <hour adjustment> -h +3 The adjustment value is added to local time (hour) to allow for different timezones. It can be positive (with or without a plus (+) sign), or negative.
-r <transparency> -r 100 Sets the transparency (visibility of underlying window). This can be any number between 30 and 255. The lower the number, the more transparent.
-s <skin name> -s neonblue This is name of skin that will be used. It can be any of the following: cpbob, cpnobob, darkblue, silver, gold, black, lightblue, green, red, rose, aqua, neonblue, bluestreak, crystalgreen, crystalblack, crystalblue, crystalred, crystalyellow, silverring, minimal, white.
-t <tooltip string> -t "Los Angeles" This is tooltip that is displayed when the mouse hovers over XCPClock. Note that quotes (") are required if string contains spaces.
-w <always on top> -w 1 Specifies whether XCPClock will remain on top of other windows. Valid values: 0 or 1.
-x <x position> -x 400 Specifies initial x display position.
-y <y position> -y 300 Specifies initial y display position.

Synchronized Move

Once you have multiple XCPClocks displayed, you may find that you need to move them. To facilitate this, clicking and dragging any XCPClock that has been started with command line options will cause all other XCPClocks to move in sync. You can move an XCPClock independently by holding down the Ctrl key while dragging the XCPClock.

Synchronized movement will only be applied to XCPClocks that have been started with command line options.

Global Close

To make displaying multiple XCPClocks more manageable, there is a selection on the right-click menu to allow you to close all running XCPClocks.

Implementation Notes

Transparent Click-thru

The most requested enhancement of the original XCPClock was transparent click-thru. This means that clicks on XCPClock would be passed through to the underlying window, as if XCPClock wasn't there. The value of this is obvious - to set transparency in XCPClock, and then be able to operate windows on screen, without having to move XCPClock out of the way.

Some readers actually suggested an easy solution: just add WS_EX_TRANSPARENT style to the dialog. Here is what MSDN says about WS_EX_TRANSPARENT:

WS_EX_TRANSPARENT - Specifies that a window created with this style is to be transparent. That is, any windows that are beneath the window are not obscured by the window. A window created with this style receives WM_PAINT messages only after all sibling windows beneath it have been updated.

OK, that sounds good in theory. In practice, a real disaster. The effect of adding WS_EX_TRANSPARENT style is to make XCPClock completely inaccessible to user. It won't receive any mouse or keyboard input. You can't right-click it, or move it by dragging. The "suggested" solution for this problem? Use global mousehook. In my opinion, this is truly evil. Another solution that is sometimes used is to create tray icon, to allow back-door UI operations when app cannot respond.

Since use of WS_EX_TRANSPARENT was out of the question, I decided to do it myself. The first thing was to define how I wanted click-thrus to work. They had to work for left mouse button, but not for right button - the right-click popup menu needed to work. And click-thrus should not work at all unless transparency is enabled (this meant transparency setting of less than 255).

I knew the key to getting click-thrus to work was in sending right message to right window. In this case, right message was obviously WM_LBUTTONDOWN, and right window was the one lying directly underneath XCPClock. But how to find it? I knew one thing that MSDN documentation doesn't say; I knew that EnumWindows() enumerated windows according to their z-order (the order that they appear on screen, from topmost to bottommost). This made it easy to find right window - I just continued enumeration until I found the first window (not XCPClock!) that contained the click point. Since XCPClock was on top of other windows, this guaranteed that window would be the one directly underneath XCPClock.

Now that I had window's HWND, what next? My overly optimistic thinking was to just send WM_LBUTTONDOWN message, followed by WM_LBUTTONUP. No matter how I tried to do this, it didn't work. Fortunately, there's another technique, almost as simple as sending a message: the SendInput() function. Of course, when I tried this, it didn't work either. It took me a while to realize that the click was being sent to XCPClock itself, since it was at top of z-order! After a little experimenting with moving XCPClock out of the way, etc., I got sequence down to as few steps as possible:

  1. Find the underlying window.
  2. Hide the XCPClock window using ShowWindow().
  3. Call SendInput() to send button down and button up events.
  4. Un-hide XCPClock with another call to ShowWindow().

To my surprise, doing these steps in the right order worked on both XP and Vista. The only (slight) downside was brief flicker when XCPClock was being hidden and un-hidden, which I now claim is a feature.

Synchronized Move

When you have used a cmd file to display several XCPClocks on screen at exact position you want, sooner or later you will want to move them. It happened to me so many times that I realized it was going to be problem for XCPClock users. I thought, wouldn't it be nice to be able to click on one of the XCPClocks and drag it somewhere, and all other XCPClocks would move with it? So I began to think of ways I could accomplish this.

The first thing that came to mind was sending messages among running XCPClock apps. I knew I could find all running XCPClock apps by simply enumerating windows, as I discussed above. Then I could use MoveWindow() to move XCPClock to a new location. While I'm sure this would work, it was not what I wanted. I wanted to move only those XCPClocks that had been started with command line options. Now things were getting complicated. After enumerating windows, I would have to send a message to each XCPClock, telling it to move itself if it was started with command line options.

Now I am going to let you in on one of my secrets. Here is how I did all the things I just mentioned, by sending only one message. The message is a unique registered windows message; it's registered because I want to broadcast it. So what's the secret? It's how I send the message - via SendMessageCallback(), which has got to be one of least-known Win32 APIs:

The SendMessageCallback function sends the specified message to a window or windows. It calls the window procedure for the specified window and returns immediately. After the window procedure processes the message, the system calls the specified callback function, passing the result of the message processing and an application-defined value to the callback function.

This sounds deceptively simple. Consider what's happening:

  1. I call one API to initiate the process.
  2. I use registered window message that is broadcast via HWND_BROADCAST to all top-level windows.
  3. I get response from every top-level app. However, only XCPClock apps respond with predetermined code (which happens by coincidence to be my CodeProject user ID). This tells me two things: first, it tells me which responses are valid - i.e., from XCPClock app that has been started with command line options; and second, it gives me HWND of XCPClock app.

Here is the call to SendMessageCallback():

::SendMessageCallback(HWND_BROADCAST, WM_XCPCLOCK_INIT, 0, 0,
    SendAsyncProc, (ULONG_PTR) m_hWnd);

Each XCPClock has its own message handler for registered message WM_XCPCLOCK_INIT:

LRESULT CXCPClockDlg::OnXCPClockInit(WPARAM /*wParam*/, LPARAM /*lParam*/)
    TRACE(_T("in CXCPClockDlg::OnXCPClockInit: 0x%X\n"), m_hWnd);

    if (__argc > 1)
        return 15759;  // respond to broadcast message with a unique code,
                       // if started with command line options
        return 0;

and here is my implementation of SendAsyncProc():

void CALLBACK SendAsyncProc(HWND hWnd,        // handle to destination window
                            UINT uMsg,        // message
                            ULONG_PTR dwData, // application-defined value
                            LRESULT lResult)  // result of message processing
    TRACE(_T("in SendAsyncProc:  hWnd=0x%X  lResult=%d\n"), hWnd, lResult);

    HWND me = (HWND) dwData;

    if ((uMsg == WM_XCPCLOCK_INIT) && (hWnd != me) && (lResult == 15759))
        // this callback is from a XCPClock app
        TRACE(_T("adding 0x%X\n"), hWnd);

The HWNDs collected in this way are stored in an array - this only happens once. Using an array of HWNDs, I then move other XCPClock apps whenever user drags the current app.

Global Close

When I was testing Synchronized Move, I quickly became tired of repeatedly closing each XCPClock. So I decided to implement a Global Close, which would close all running XCPClocks. This was fairly simple to do, because I knew how to enumerate all windows, and furthermore I knew that XCPClock had a unique class name which I could use to find running XCPClocks.

This unique class name was implemented by hand-editing XCPClock.rc, and adding CLASS field to dialog template, as highlighted below:

CLASS "<span class="code-string">TheCodeProjectClock"</span>
FONT 8, "MS Sans Serif", 0, 0, 0x1
    LTEXT           "",IDC_FRAME,0,0,90,70

Once the dialog template was modified, the unique class name could be used to register new class for XCPClock dialog:

BOOL CXCPClockApp::InitApplication() 
    wc.lpfnWndProc = DefDlgProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = DLGWINDOWEXTRA;
    wc.hInstance = AfxGetInstanceHandle();
    wc.hIcon = LoadIcon(IDR_MAINFRAME);
    wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW + 1;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = _T("<span class="code-string">TheCodeProjectClock");</span>
    return CWinApp::InitApplication();

Now I could use this unique class name to find running XCPClocks. First, I began window enumeration:

void CXCPClockDlg::OnCloseAll() 
    ::EnumWindows(EnumWindowsProcCloseAll, (LPARAM)m_hWnd);
    GetParent()->SendMessage(WM_CLOSE);    // close this instance

In the enumeration function, I check for unique class name:

BOOL CALLBACK EnumWindowsProcCloseAll(HWND hWnd,    // handle to window
                                     LPARAM lParam) // HWND of caller
    HWND me = (HWND) lParam;

    if (hWnd == me)
        // this is my window - ignore
        return TRUE;

    TCHAR szClassName[100];
    ::GetClassName(hWnd, szClassName, sizeof(szClassName)/sizeof(TCHAR)-1);
    szClassName[sizeof(szClassName)/sizeof(TCHAR)-1] = 0;

    if (_tcsicmp(szClassName,  _T("<span class="code-string">TheCodeProjectClock")</span>) == 0)
        TRACE(_T("closing window 0x%X\n"), hWnd);
        ::SendMessage(::GetParent(hWnd), WM_CLOSE, 0, 0);

    return TRUE;

Note that the WM_CLOSE message is sent to dialog's parent, which is actually an invisible window that is created in CXCPClockApp::InitInstance(); this keeps XCPClock from showing up on the task bar.



Revision History

Version 1.1 - 2008 August 23

  • Minor bug fixes
  • Changed to automatic link to msimg32.lib (previously you had to add this lib to the project link modules)
  • Added new skins
  • Added click-thru when transparency is enabled (less than 255)
  • Added shortcuts for setting transparency
  • Added automatic hand color for new transparent clock faces
  • Added command line options
  • Added synchronized move
  • Added global close option to popup menu
  • Added option to show/hide second hand
  • Added VS2005 project

Version 1.0 - 2006 March 24

  • Initial public release.


This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Hans Dietrich
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
JJMatthews18-Feb-12 2:16
memberJJMatthews18-Feb-12 2:16 
webroy200020-Apr-10 11:03
memberwebroy200020-Apr-10 11:03 
Hans Dietrich20-Apr-10 13:35
mentorHans Dietrich20-Apr-10 13:35 
GeneralRe: Size if the clock Pin
webroy200021-Apr-10 4:19
memberwebroy200021-Apr-10 4:19 
GeneralExactly what I was looking for! [modified] Pin
gleat26-Apr-09 13:27
membergleat26-Apr-09 13:27 
GeneralRe: Exactly what I was looking for! Pin
Hans Dietrich26-Apr-09 20:15
mvpHans Dietrich26-Apr-09 20:15 
GeneralNice one, Hans Pin
DavidCrow3-Sep-08 3:21
mvpDavidCrow3-Sep-08 3:21 
GeneralBrilliant Pin
Matthew Faithfull29-Aug-08 2:53
memberMatthew Faithfull29-Aug-08 2:53 
Generalbeautiful, it will be better to show AM/PM after day, such as 29AM Pin
brent.wjh28-Aug-08 16:34
memberbrent.wjh28-Aug-08 16:34 
GeneralSuggestion Pin
Dandy Cheung27-Aug-08 16:17
memberDandy Cheung27-Aug-08 16:17 
GeneralRe: Suggestion Pin
Hans Dietrich27-Aug-08 20:58
mvpHans Dietrich27-Aug-08 20:58 
GeneralRe: Suggestion Pin
Richard Wyllie17-Oct-12 15:22
memberRichard Wyllie17-Oct-12 15:22 
JokeNeat Pin
Ben Daniel24-May-07 12:54
memberBen Daniel24-May-07 12:54 
GeneralFeatures Request..! (Digital Display and World Time) Pin
ana_v12319-Sep-06 1:28
memberana_v12319-Sep-06 1:28 
GeneralRe: Features Request..! (Digital Display and World Time) Pin
jfdoubell25-May-07 0:29
memberjfdoubell25-May-07 0:29 
GeneralRe: Features Request..! (Digital Display and World Time) Pin
s-a-s29-May-07 5:02
members-a-s29-May-07 5:02 
GeneralRe: Features Request..! (Digital Display and World Time) Pin
tkboy859-Aug-07 16:13
membertkboy859-Aug-07 16:13 
GeneralMemory Leaks Pin
mcv99910-Aug-06 21:37
membermcv99910-Aug-06 21:37 
GeneralDouble click should take you to the code project web site! Pin
Shaun Harrington9-Jul-06 5:26
memberShaun Harrington9-Jul-06 5:26 
GeneralBob on black... Pin
Shaun Harrington9-Jul-06 5:19
memberShaun Harrington9-Jul-06 5:19 
Generalmy god,i like it, thanks! Pin
win98ddk10-Apr-06 17:28
memberwin98ddk10-Apr-06 17:28 
GeneralCool Pin
Darka6-Apr-06 21:27
memberDarka6-Apr-06 21:27 
QuestionHow to hide the clock from Alt-Tab chooser ? Pin
Fred W27-Mar-06 22:31
memberFred W27-Mar-06 22:31 
AnswerRe: How to hide the clock from Alt-Tab chooser ? Pin
AndrewVos28-Mar-06 10:21
memberAndrewVos28-Mar-06 10:21 
GeneralRe: How to hide the clock from Alt-Tab chooser ? Pin
Fred W28-Mar-06 20:37
memberFred W28-Mar-06 20:37 
AnswerRe: How to hide the clock from Alt-Tab chooser ? Pin
Richard Wyllie17-Oct-12 15:50
memberRichard Wyllie17-Oct-12 15:50 
GeneralOne problem. Pin
WREY27-Mar-06 5:37
memberWREY27-Mar-06 5:37 
GeneralRe: One problem. Pin
Hans Dietrich5-Jul-06 22:25
memberHans Dietrich5-Jul-06 22:25 
GeneralI like it :) Pin
newmodel27-Mar-06 4:23
membernewmodel27-Mar-06 4:23 
GeneralScreen Saver Pin
brianma27-Mar-06 2:21
memberbrianma27-Mar-06 2:21 
Generalneat! Pin
dighn24-Mar-06 12:12
memberdighn24-Mar-06 12:12 
GeneralGood Job Pin
rodmra24-Mar-06 6:56
memberrodmra24-Mar-06 6:56 
GeneralThis is the good one! Pin
Emilio CL24-Mar-06 3:48
memberEmilio CL24-Mar-06 3:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.151002.1 | Last Updated 27 Aug 2008
Article Copyright 2006 by Hans Dietrich
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid