
Introduction
This article demonstrates an effective use of the Window Finder utility that I presented in my CodeProject article last week: MS Spy++ Style Window Finder.
The demo app is a Sys Tray utility that activates a context menu when doubled-clicked. Using the demo app, you are able to manipulate the Z-order of a top-level window or dialog box, making it always on top of any other window on the desktop. Although my main intension is to give a demonstration of one possible use of the Window Finder utility, I had always wanted to have such a Window Floater facility for a long time. I first got the idea when I was trying to casually watch a movie on CD-ROM while doing some programming work at the same time. The movie player software had no facility to stay on top of other windows which meant I had to make it share the monitor screen with Visual Studio. It was very annoying.
At last now, we have a neat little utility to help make any top-level window or dialog box stay on top.
Usage
Start the demo app and the Window Floater icon will appear in the System Tray area:

Double-click on the icon and a context menu will appear. There will be 4 menu items:
- Float A Window - this option will invoke the "Search Window" dialog box which will allow you to select a window to float on-top of all other windows on the desktop.
- Sink A Window - this option does not actually "sink" a window. It will simply make a window non-topmost. If the selected window is a top-most window, it will become non-topmost. If it is not a top-most window, it will remain as it is. The "Search Window" dialog box will be used to select the target window.
- About - displays an about message box.
- Shutdown - shuts down the entire Sys Tray app.
Once a window is selected, its information will be displayed in the "Search Window" dialog box and once you click on the "OK" button, the selected window will become one of the windows in the list of top-most windows in the OS.
Summary of how it works
Please read through my "MS Spy++ Window Finder" article to understand how the window finder part of this demo app works. I have made extensive re-use of the source codes of the Window Finder app. The following is a summary of the enhancements and transformations that were made to the original Window Finder source codes:
- From Standard Window App to System Tray App. This requires:
- Adding a Sys Tray icon.
- Hiding the main window of the app.
- Defining a custom window message for processing Sys Tray activities.
- Defining a context menu to be activated when the user double-clicks on the Sys Tray icon.
- Defining handlers for the context menu items.
- Modifying the Window Searching algorithm.
- A small modification was made to the
CheckWindowValidity()
function. We now add a new condition to ensure that only top-level windows or dialog boxes can be selected.
- After a window has been selected, we proceed to either make the selected window float to the top or remove this top floating property from the window.
- The
SetWindowPos()
Win32 API is the main driving force behind the floating/un-floating operation.
Detailed explanation of how it works
In order to make a System Tray app, I have removed the WS_VISIBLE
style from the main window of the application and explicitly hidden it after creation:
dwStyle = WS_OVERLAPPEDWINDOW;
g_hwndMainWnd = CreateWindow
(
szMainWindowClassName,
WINDOW_FLOATER_MAIN_WINDOW_TITLE,
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
...
...
...
ShowWindow(g_hwndMainWnd, SW_HIDE);
UpdateWindow(g_hwndMainWnd);
The main window will still be required to process messages for the Sys Tray icon. I have also defined a new function InitialiseShellModules()
which is called within WinMain()
. This function will perform initializations for the Sys Tray icon. This function is listed below:
BOOL InitialiseShellModules()
{
NOTIFYICONDATA nid;
BOOL bRetTemp = FALSE;
BOOL bRet = TRUE;
g_hIconSysTray = (HICON)LoadImage
(
(HINSTANCE)g_hInst,
(LPCTSTR)MAKEINTRESOURCE(IDI_ICON_SYS_TRAY),
(UINT)IMAGE_ICON,
(int)16,
(int)16,
(UINT)0
);
memset (&nid, 0, sizeof(NOTIFYICONDATA));
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = g_hwndMainWnd;
nid.uID = ICON_ID;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.hIcon = g_hIconSysTray;
nid.uCallbackMessage = WM_SYS_TRAY_MESSAGE;
strcpy (nid.szTip, WINDOW_FLOATER_TOOL_TIP);
bRetTemp = Shell_NotifyIcon
(
(DWORD)NIM_ADD,
(PNOTIFYICONDATA)&nid
);
g_dwLastError = GetLastError ();
return bRet;
}
The counterpart of UninitializeApplication()
is UninitialiseShellModules()
which will basically unregister our icon from the System Tray.
As can be seen in InitialiseShellModules()
, we have indicated that the main application window g_hwndMainWnd
is to receive the WM_SYS_TRAY_MESSAGE
message meant for our icon. Hence, we provide a WM_SYS_TRAY_MESSAGE
message handler in MainWndProc()
:
case WM_SYS_TRAY_MESSAGE:
{
lRet = 0;
bCallDefWndProc = TRUE;
if (wParam == ICON_ID)
{
if (lParam == WM_LBUTTONDBLCLK)
{
if (g_bAllowContextMenu)
{
DisplayContextMenu (hwnd);
}
else
{
if (g_hwndSearchDialog)
{
SetForegroundWindow (g_hwndSearchDialog);
}
}
}
}
break;
}
When the user double-clicks on the icon, we first check the BOOL
flag g_bAllowContextMenu
to see if we allow the display of the context menu. This is important because we must not allow the user to select a window to "sink" while the user is currently selecting a window to "float". We may, of course, opt to disable the "Float A Window" menu item. This is a splendid idea, especially if there are any other options in the context menu not related directly to window selection. This will certainly complicate the code further but it can be done.
If we do not allow the context menu to be displayed, and if the "Search Window" dialog box is active (non-null value in g_hwndSearchDialog
), we bring the "Search Window" to the foreground immediately. This provides greater convenience for the user because once a window has been set to be the top-most window, it will be of a high Z-order than the "Search Window" dialog and so the "Search Window" dialog box may be obscured or hidden by it. By bringing the "Search Window" dialog to the foreground when the user double-clicks the Sys Tray icon, the user can avoid minimizing windows in order to get to it.
The various handlers for the context menu items are listed below:
case WM_COMMAND:
{
WORD wNotifyCode = HIWORD(wParam);
WORD wID = LOWORD(wParam);
HWND hwndCtl = (HWND)lParam;
if (wNotifyCode == 0)
{
if (wID == IDM_CONTEXT_ABOUT)
{
MessageBox (NULL, ABOUT_WINDOW_FLOATER,
WINDOW_FLOATER_MAIN_WINDOW_TITLE, MB_OK);
}
if (wID == IDM_CONTEXT_FLOAT_A_WINDOW)
{
g_bFloatOrSinkWindow = TRUE;
PostMessage (hwnd, WM_START_SEARCH_WINDOW, 0, 0);
}
if (wID == IDM_CONTEXT_SINK_A_WINDOW)
{
g_bFloatOrSinkWindow = FALSE;
PostMessage (hwnd, WM_START_SEARCH_WINDOW, 0, 0);
}
if (wID == IDM_CONTEXT_SHUTDOWN)
{
PostMessage (hwnd, WM_CLOSE, 0, 0);
}
lRet = 0;
bCallDefWndProc = FALSE;
}
else
{
bCallDefWndProc = TRUE;
}
break;
}
Let us examine the handlers for IDM_CONTEXT_FLOAT_A_WINDOW
and IDM_CONTEXT_SINK_A_WINDOW
. They each will post the WM_START_SEARCH_WINDOW
message back to the main window which will eventually call on StartSearchWindowDialog()
to start the "Search Window" dialog box:
case WM_START_SEARCH_WINDOW :
{
lRet = 0;
bCallDefWndProc = FALSE;
StartSearchWindowDialog (hwnd);
break;
}
The difference between them is the use of the global BOOL
flag g_bFloatOrSinkWindow
which indicates whether a Window "Float" or "Sink" operation is taking place.
This flag is later used in the IDOK
handler for the "Search Window" dialog box:
if ((wID == IDOK) || (wID == IDCANCEL))
{
bRet = TRUE;
if ((wID == IDOK) && (g_hwndFoundWindow))
{
FloatOrSinkWindow (g_hwndFoundWindow, g_bFloatOrSinkWindow);
}
g_bAllowContextMenu = TRUE;
g_hwndSearchDialog = NULL;
EndDialog (hwndDlg, wID);
}
We use this flag in the call to FloatOrSinkWindow()
which will be described next. The FloatOrSinkWindow()
is the function that sets out to either "float" or "sink" a target window.
BOOL FloatOrSinkWindow (HWND hwndToFloatOrSink, BOOL bFloat)
{
HWND hwndInsertAfter = NULL;
BOOL bRet = FALSE;
if (bFloat)
{
hwndInsertAfter = HWND_TOPMOST;
}
else
{
hwndInsertAfter = HWND_NOTOPMOST;
}
bRet = SetWindowPos
(
(HWND)hwndToFloatOrSink,
(HWND)hwndInsertAfter,
(int)0,
(int)0,
(int)0,
(int)0,
(UINT)(SWP_NOMOVE | SWP_NOSIZE)
);
return bRet;
}
Its structure is very simple and it is geared towards calling the SetWindowPos()
Win32 API.
Also, as mentioned in the summary section above, a small modification was made to the CheckWindowValidity()
function:
lWindowLong = GetWindowLong (hwndToCheck, GWL_STYLE);
if (lWindowLong & WS_CHILD)
{
bRet = FALSE;
goto CheckWindowValidity_0;
}
This will ensure that only non-child windows are considered valid target windows. Therefore, only top-level windows and dialog boxes are selectable.
In Conclusion
Let me say a very big thanks to all the readers who have sent me very supportive comments and have given very good ratings to my "MS Spy++ Style Window Finder" article.
I'll continue to look for creative ways to incorporate the basic Window Finder utility into various types of applications. In fact, I'm thinking of creating a Visual Studio Add-in that can use the Window Finder to select an app (from its main window) to attach to for debugging.
I hope this article will be useful to all. My best wishes to all in this New Year 2002.