This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

MIND

C++ Q&A

Create a Dialog while Keeping it off that Pesky Taskbar

Paul DiLascia

In my February 2000 column in MSJ, a reader asked if there�s a way to determine how long a machine has been idle. I mentioned a new API function, GetLastInputInfo, that exists only on Windows NT® and Windows® 2000, and showed how you can use systemwide mouse and keyboard hooks to implement such a function for Windows 9x.
      As a couple of readers pointed out, I forgot to mention a new hook in Windows 2000, WH_FOREGROUNDIDLE. Windows calls this hook whenever the application�s foreground thread is about to go idle; that is, when there�s no input waiting to be processed. WH_FOREGROUNDIDLE probably isn�t the right tool for the original question since it refers to the applicationâ€"not the entire systemâ€"going idle. However, it�s worthy of mention. If you want to write a program that "wakes up" after the system has been idle for a certain period, you�re better off using a timer and GetLastInputInfo.
Figure 1 Applet Dialog in Taskbar
Figure 1Applet Dialog in Taskbar

      In the November 1999 issue of MSJ (https://www.microsoft.com/msj/1199/c++/c++1199.htm), I showed how to build a miniframework for implementing a Control Panel applet. A number of readers pointed out various little bugs and one major one: my applet dialogs appear in the Windows taskbar, unlike the Microsoft® applets, which do not (see Figure 1). A couple of readers asked: how do you make the Control Panel applet disappear from the taskbar? This is the perfect segue to another question I was recently asked, so let me answer it first, and then I�ll return to the Control Panel applets.

Q

How do I build a stealth dialogâ€"one that doesn�t appear on the taskbar? I�ve tried manually setting the WS_EX_TOOLWINDOW style as it says in the documentation, but this only shrinks the caption bar; the dialog still shows up on the taskbar.

Martin MacRobert

A

As is so often the case in Windows, the answer involves a scratch-your-head-while-you-rub- your-stomach-and-touch- your-left-pinky-to-your-right-knee kind of solution. I will simply tell you the answer: you must create your dialog as a child of an invisible window that has WS_EX_TOOLWINDOW set, and you must make sure your dialog has the WS_EX_APPWINDOW style turned off. If you read the documentation�s fine print, you might figure this outâ€"but only if you use a powerful magnifying glass.
      I wrote a little program called HideDlg that displays a simple dialog that doesn�t appear in the taskbar. Figure 2 shows the source code. Here�s a summary of the relevant sections of code.

  • CMyApp::CInitInstance loads a frame window in the normal MFC way for a non-doc/view window, but the window is created without WS_VISIBLE. After creating the frame, I use it as the parent of the dialog. Even though this frame window never appears, you must give it a menu in your resource file, or else MFC will become unhappy.
  • CMainFrame::PreCreateWindow sets the WS_EX_TOOLWINDOW style. This hides it from the taskbar and also gives it the small caption. Since the frame is invisible, you don�t care how big the caption is.
  • The dialog must have WS_EX_APPWINDOW turned off.

      This last item is the missing trick that stumps most people when they try stealth dialogs at home. By default, the Visual C++® IDE gives you this style with the line

EXSTYLE WS_EX_APPWINDOW

in your resource file. You can delete the line or uncheck the style in the dialog properties. The subterfuge of creating a hidden parent WS_EX_TOOLWINDOW window is necessary only if you want a dialog with a normal caption; if you�re happy with the smaller caption, you can just use WS_EX_TOOLWINDOW and not WS_ EX_APPWINDOW (however, when I tried it, I also got a strange display glitch).
      Now that you know how to make a stealth dialog, how do you make a stealth Control Panel applet? Well, you have to turn off WS_EX_APPWINDOW in your dialog just as before, but instead of creating your dialog as a child of an invisible WS_EX_TOOLWINDOW window, all you have to do is use the one Windows gives you. If you recall from my November 1999 column in MSJâ€"or even if you don�tâ€"the way the Control Panel launches your applet is by sending your DLL a CPL_DBLCLK message. When it does, it passes an HWND to use as your dialog�s parent window. When my CPanel framework gets CPL_DBLCLK, it converts the HWND to a CWnd and calls CCPApplet::OnLaunch:
LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, ...)
{
CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
if (pw) {

  if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) { 
     // launch prop sheet 
  } else if (pw->IsKindOf(RUNTIME_CLASS(CDialog))) { 
     // launch dialog 
  } 

} return pw==NULL; }

Unfortunately, my original implementation ignored pWndCpl, which is why everyone�s applets appear in the taskbar. So to make your dialog disappear from the taskbar, you simply have to create the dialog using the supplied window as its parent.
      However, there�s a technical problem, which is why I punted pWndCpl in the original CPanel (forgetting that the dialog would then appear in the taskbar): the parent window of a dialog or property sheet must be specified when you create the dialog or property sheet, as an argument to the constructor, like so:
CDialog* pDlg = new CDialog(pParent);

But CPanel doesn�t create the CDialog object directly; it creates the object through CRuntimeClass::CreateObject, which doesn�t let you pass arguments to the constructor.
      What you need to do is something like this:
CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
pw->SetParent(pWndCpl);

But this won�t work because CWnd::SetParent assumes the window has been created already, which it hasn�t. Remember, in MFC object creation and window creation are separate events. MFC doesn�t create the dialog until you call DoModal.
      So how does the constructor set the parent? When you pass a parent window pointer to CDialog::CDialog, it stores the pointer in a member m_pParentWnd, which it uses later in DoModal. So you need to manually set m_pParentWnd after creating the object, but before running the dialog. But you can�t do that either because CDialog::m_pParentWnd is protected! Sigh.
      What to do? Well, there are a couple of solutions. You could store pWndCpl as a global, and then have your dialog or property sheet constructor grab it in its constructor.
CMyDialog::CMyDialog()
{
m_pParentWnd = pTheGlobalCplWnd;
���
}

>But this requires making the programmer (that�s you) remember to set m_pParentWnd. Remember, I�m trying to write a framework here, one that even intelligent chimpanzees can use. The framework should do everything it can automatically. Plus, globals are gaucheâ€"especially in multithreaded situations. In practice, the Control Panel window will always be the same (there�s only one, after all), but why use a global when you don�t have to, especially when there�s an even more disgusting hack: FakePre-429ba3ab72dd4fdeada87ee9a2793c39-a506c6e5c4d9421eb9965e1a958dc666      CHackDialog is a semantic sleight of hand to make m_pParentWnd public by providing a function to set it. I pulled a similar stunt for CPropertySheet, too. So now I can write the final OnLaunch function. FakePre-2153f058e519488fa0349131dbffd6e8-963554d1fe524d3aa4d28c1c74b2c360The complete code for the new CPanel.cpp is shown in Figure 3.
      Once the framework uses the proper parent window for its dialogs and property sheets, they magically disappear from the taskbarâ€"assuming you also remember to turn off the WS_EX_ APPWINDOW style for your dialog.
      How could you have figured out the magic voodoo on your own, without reading my column? The same way I did, by examining a real stealth dialog with Spy++. If you run the Control Panel and open one of the appletsâ€"Mouse Properties, sayâ€"you�ll discover that the applet window is a dialog with WS_EX_APPWINDOW turned off, parented to an invisible window called RunDLL, which has WS_EX_TOOLWINDOW set. This is the window that the Control Panel passes as pWndCpl.

Paul DiLascia is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at cppqa@microsoft.com or https://www.dilascia.com.

From the May 2000 issue of MSDN Magazine.