WindowFloater - A System Tray Utility To Make A Window Float To The Top.






4.67/5 (9 votes)
A System Tray utility to make any window float to the top of the Z-Order.
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.
- A small modification was made to the
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:
// Enhancement to WindowFinder - main window // will not have WS_VISIBLE style. dwStyle = WS_OVERLAPPEDWINDOW; // Create the main window. g_hwndMainWnd = CreateWindow ( szMainWindowClassName, // name of window class WINDOW_FLOATER_MAIN_WINDOW_TITLE, // title dwStyle, // window style - normal CW_USEDEFAULT, // X coordinate - let Windows decide CW_USEDEFAULT, // Y coordinate - let Windows decide CW_USEDEFAULT, // width - let Windows decide CW_USEDEFAULT, // height - let Windows decide NULL, // no parent window NULL, // no override of class menu hInstance, // handle for this instance NULL // no additional arguments ); ... ... ... // Display the window. // Enhancement to WindowFinder - hide the main window. 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 ( // handle of the instance that contains the image (HINSTANCE)g_hInst, // name or identifier of image (LPCTSTR)MAKEINTRESOURCE(IDI_ICON_SYS_TRAY), (UINT)IMAGE_ICON, // type of image (int)16, // desired width (int)16, // desired height (UINT)0 // load flags ); 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, // message identifier (PNOTIFYICONDATA)&nid // pointer to structure ); 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) { // Enhancement to Window Finder - display context menu only if we // currently allow it. If context menu is disallowed, and the // Search Window Dialog is alive, put it in the foreground. 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); // notification code WORD wID = LOWORD(wParam); // item, control, or accelerator identifier HWND hwndCtl = (HWND)lParam; // handle of control if (wNotifyCode == 0) { // Message is from a menu. if (wID == IDM_CONTEXT_ABOUT) { MessageBox (NULL, ABOUT_WINDOW_FLOATER, WINDOW_FLOATER_MAIN_WINDOW_TITLE, MB_OK); } // Enhancement to Window Finder - perform a float window operation. if (wID == IDM_CONTEXT_FLOAT_A_WINDOW) { g_bFloatOrSinkWindow = TRUE; // We now wish to float a window. PostMessage (hwnd, WM_START_SEARCH_WINDOW, 0, 0); } // Enhancement to Window Finder - perform a sink window operation. if (wID == IDM_CONTEXT_SINK_A_WINDOW) { g_bFloatOrSinkWindow = FALSE; // We now wish to sink a window. 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; // Enhancement to Window Finder - make found window float to the top. if ((wID == IDOK) && (g_hwndFoundWindow)) { FloatOrSinkWindow (g_hwndFoundWindow, g_bFloatOrSinkWindow); } // Enhancement to Window Finder - // now allow activation of context menu. g_bAllowContextMenu = TRUE; // Enhancement to Window Finder - // set global var "g_hwndSearchDialog" to NULL. 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, // handle of window (HWND)hwndInsertAfter, // placement-order handle (int)0, // horizontal position (int)0, // vertical position (int)0, // width (int)0, // height (UINT)(SWP_NOMOVE | SWP_NOSIZE) // window-positioning flags ); 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:
// Enhancement to Window Finder - the window must // be a top level window or a dialog box. 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.