Click here to Skip to main content
15,885,278 members
Articles / Desktop Programming / MFC
Article

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

Rate me:
Please Sign up or sign in to vote.
4.67/5 (9 votes)
5 Jan 2002CPOL6 min read 96K   2.5K   46   3
A System Tray utility to make any window float to the top of the Z-Order.

Sample Image - WindowFloater.jpg

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:

Image 2

Double-click on the icon and a context menu will appear. There will be 4 menu items:

  1. 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.
  2. 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.
  3. About - displays an about message box.
  4. 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:

  1. From Standard Window App to System Tray App. This requires:
    1. Adding a Sys Tray icon.
    2. Hiding the main window of the app.
    3. Defining a custom window message for processing Sys Tray activities.
    4. Defining a context menu to be activated when the user double-clicks on the Sys Tray icon.
    5. Defining handlers for the context menu items.
  2. Modifying the Window Searching algorithm.
    1. 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.
    2. 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.
    3. 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:

// 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.

License

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


Written By
Systems Engineer NEC
Singapore Singapore
Lim Bio Liong is a Specialist at a leading Software House in Singapore.

Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.

Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area.

Comments and Discussions

 
GeneralIt's really very good Pin
vcmira5-Nov-03 20:21
vcmira5-Nov-03 20:21 

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

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