![]() |
Languages »
C / C++ Language »
General
Intermediate
License: The Code Project Open License (CPOL)
XCPClock - CodeProject ClockBy Hans DietrichXCPClock displays CodeProject clock with four skins. |
C++, Windows, Visual Studio, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
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:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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:
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. |
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.
To make displaying multiple XCPClocks more manageable, there is a selection on the right-click menu to allow you to close all running XCPClocks.
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:
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:
ShowWindow().
SendInput() to send button down and button up events.
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.
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:
This sounds deceptively simple. Consider what's happening:
HWND_BROADCAST to all top-level windows.
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
else
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);
g_hWndArray.Add(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.
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:
IDD_XCPCLOCK_DIALOG DIALOGEX 0, 0, 92, 70
STYLE WS_POPUP | WS_VISIBLE
EXSTYLE WS_EX_APPWINDOW
CLASS "TheCodeProjectClock"
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
LTEXT "",IDC_FRAME,0,0,90,70
END
Once the dialog template was modified, the unique class name could be used to register new class for XCPClock dialog:
BOOL CXCPClockApp::InitApplication()
{
WNDCLASS wc;
wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
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("TheCodeProjectClock");
::RegisterClass(&wc);
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("TheCodeProjectClock")) == 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.
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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 27 Aug 2008 Editor: Sean Ewington |
Copyright 2006 by Hans Dietrich Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |