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.


February 1999

Microsoft Systems Journal Homepage

C++ Q&A

Code for this article: Feb99CQA.exe (38KB)

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 askpd@pobox.com or http://www.pobox.com/~askpd.

     Q: I have a menu with several items, one of which is a submenu. Normally, I use Class Wizard and ON_UPDATE_COMMAND_UI to enable or disable menu items, but I'm not able to use the same technique to gray a submenu name itself because it doesn't have a command ID associated with it. Is it possible to gray a submenu item using MFC?

     Bill W. Gibbons

     A: Yes. In fact, it's easy-if you know the trick! Normally, to enable or disable a menu item, you write an ON_UPDATE_COMMAND_UI handler for it. For example, if you have a View | Foozle command whose ID is ID_VIEW_FOOZLE, you'd write a command UI handler like so:

BEGIN_MESSAGE_MAP(CMyView, CView)
    ON_UPDATE_COMMAND_UI(ID_VIEW_FOOZLE,                             OnViewFoozle)
END_MESSAGE_MAP()

void CMyView::OnViewFoozle(
    CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bEnableFoozle);
}

     This is normal MFC 101. But as you noticed, a menu item that's a popup submenu has no ID. Or rather, the ID is -1. You might try using -1 as the ID in your ON_UPDATE_COMMAND_UI macro. Hey, why not? But aside from the minor detail that using -1 provides no way to distinguish multiple submenus (since they all have ID = -1), it doesn't work. You can add an ON_UPDATE_COMMAND_UI handler with ID = -1 all you want; MFC won't call it.

     To comprehend this conundrum, you have to look inside MFC, into the code that updates your menu items. It happens when CFrameWnd gets a WM_INITMENUPOPUP message.

// simplified for clarity
void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, ...)
{
    CCmdUI state;
    state.m_pMenu = pMenu;
    for (state.m_nIndex = /* 1 to nItems */) {
        state.m_nID = 
            pMenu->GetMenuItemID(state.m_nIndex);

        // now route to message maps
        state.DoUpdate(...);
    }
}

     I've omitted a lot of gnarly code to show you the main idea: MFC creates a CCmdUI object, initializes it successively with each menu item index/ID, and routes it through the system. CCmdUI::DoUpdate is the $64,000 function. It grovels over your message maps for an ON_UPDATE_COMMAND_UI handler that matches state.m_nID, and calls your handler if it finds one.

     But what happens if the menu item is a submenu? Among the lines I omitted in the previous fragment are the following, inside the loop:

// also simplified
if (/* item is a submenu */) {
    state.m_pSubMenu = 
        pMenu->GetSubMenu                      (state.m_nIndex);
    state.m_nID =                 state.m_pSubMenu->
           GetMenuItemID(0);
    state.DoUpdate(...);
}

     In other words, if the menu item is a submenu item, MFC sets state.m_pSubMenu to a pointer to the submenu (CMenu*), and m_nID to the ID of the first item in the submenu, before routing as normal. What this means from your perspective is that you can update the submenu item by writing an update handler for the first item in the submenu. But wait a minute-how do you distinguish between updating the submenu item itself and updating the first item? Answer: by looking at pCmdUI->m_pSubMenu. If this member is non-NULL, MFC is asking you to update the first item in the submenu; if it's NULL, MFC is asking you to update the first item. Pretty clever, eh?

     I wrote a little program, GRAYSUB, that shows how it works. GRAYSUB has a menu item with a submenu that has two commands: Foo and Enable Foo (see Figure 1). Enable Foo enables/disables the Foo command, which is the first item in the submenu. There's also a Disable Submenu command in the main-level popup that enables/disables the entire submenu. Figure 2 shows the submenu disabled. All the relevant command UI handlers are in CMainFrame. The interesting one is CMainFrame::OnUpdateFoo.

    

Figure 1 GRAYSUB

    

Figure 2 Submenu Disabled

void CMainFrame::OnUpdateFoo(CCmdUI *pCmdUI)
{
    if (pCmdUI->m_pSubMenu) {
        // update submenu item (File | Submenu)
        UINT fGray = m_bEnableSubmenu ? 0 : MF_GRAYED;
        pCmdUI->m_pMenu->EnableMenuItem(
                pCmdUI->m_nIndex, MF_BYPOSITION|fGray);

    } else
        // update first submenu item (File|Submenu|Foo)
        pCmdUI->Enable(m_bEnableFoo);
}

     As I explained, GRAYSUB uses the same ON_COMMAND_UPDATE_UI handler to update both the submenu item (File | Submenu) and the first item in the submenu (File | Submenu | Foo). If pCmdUI->m_pSubMenu is non-NULL, the CCmdUI object represents the submenu item; otherwise it's the normal first command item. In the former case, pCmdUI->m_pMenu is the parent menu and m_nIndex is the index of the subitem in that menu. To enable or disable this item, you can't call CCmdUI::Enable; you must call CMenu::EnableMenuItem with the appropriate flags as I've done in the previous code. Figure 3 shows the full source code for CMainFrame, with all the other command UI handlers.

     Q:I have an application that adds an icon to the system tray. When Windows® Explorer goes down, many times it restarts automatically, but has lost its tray icons. Since my application is still running, do you know of a way I can detect that I need to re-add my tray icon to get my application UI back without requiring a reboot?

     Jeff Multhaup

     Boise, Idaho

     A: Sure, it's easy-provided you have Windows 98 or the Microsoft® Internet Explorer 4.0 desktop installed. Whenever Internet Explorer 4.0 starts the taskbar, it broadcasts a registered message TaskbarCreated to all top-level parent windows. This is your cue to recreate the icons. If you're using MFC, all you have to do is define a global variable to hold the registered message and implement an ON_REGISTERED_MESSAGE handler for it.

const UINT WM_TASKBARCREATED = 
    ::RegisterWindowMessage(_T("TaskbarCreated"));

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_REGISTERED_MESSAGE(WM_TASKBARCREATED, 
                          OnTaskBarCreated)
END_MESSAGE_MAP(CMainFrame, CFrameWnd)

     The handler itself should reinstall whatever icons you need.

LRESULT CMainFrame::OnTaskBarCreated(WPARAM wp, LPARAM lp)
{
    VERIFY(InstallIcons());
    return 0;
}

BOOL CMainFrame::InstallIcons()
{
    NOTIFYICONDATA nid; 
    //
    // stuff nid with args
    //
    return Shell_NotifyIcon(NIM_ADD, &nid);
}

     What could be easier? You should implement InstallIcons as a separate function instead of calling Shell_NotifyIcon directly from OnTaskBarCreated since presumably you will want to also call it when your app starts up.

     As soon as I learned about TaskbarCreated, I dug out the CTrayIcon class from my March 1996 column-how time flies-and added some code so it automatically does the right thing without you having to even lift a pinky. (Well, you have to use the class, Sherlock.) If you recall, CTrayIcon is a class that manages a single tray icon. (If you want more than one, use a separate CTrayIcon instance for each.) To use CTrayIcon, all you have to do is create an instance of m_trayIcon in your CMainFrame and install it like so:

// in CMainFrame::OnCreate
m_trayIcon.SetNotificationWnd(this, 
    WM_MY_TRAY_NOTIFICATION);
m_trayIcon.SetIcon(IDI_MYICON);

     WM_MY_TRAY_NOTIFICATION is your own private message that the tray icon sends when something happens, like the user clicking on the icon. For a vanilla tray icon, you don't have to process it since CTrayIcon does all the right stuff. When you construct a CTrayIcon, you give it a resource ID. CTrayIcon looks for a context menu with this ID, and if there is one it automatically handles right-clicks on the tray icon by invoking the menu. If the user double-clicks the icon, CTrayIcon will execute the first command in this menu. So all you really have to do is create a resource menu, pass the ID to the constructor, and leave the driving to CTrayIcon. You only need to process WM_MY_TRAY_NOTIFICATION if you want to do something nonstandard. For more details, read the March 1996 issue, or download this month's source code from http://www.microsoft.com/msj.

     To add the automatic icon recreation feature, I used the ever-more-ubiquitous CSubclassWnd class that I introduced in my June 1997 column. I can no longer program without CSubclassWnd; it's by far the most useful class in my bag of tricks. (I apologize for constantly referring to back issues, but full source code is always available online.) CSubclassWnd lets you intercept messages sent to another window. CTrayIcon uses it to intercept the TaskbarCreated message so you don't have to write a handler for it-which is way cool. CSubclassWnd works by installing its own window proc ahead of MFC's.

     When CMainFrame calls CTrayIcon::SetNotificationWnd, CTrayIcon calls GetTopLevelParent to get the notification window's top-level parent window, then installs a CSubclassWnd to hook your window's top-level parent window (see Figure 4). Remember, Windows only sends TaskbarCreated to top-level parents. It's like WM_QUERYNEWPALETTE, WM_SYSCOLORCHANGE, and other top-level messages. Now when Windows sends TaskbarCreated, control passes first to CTrayIcon::CTrayHook::WindowProc.

     CTrayIcon::OnTaskBarCreate is a new virtual CTrayIcon function whose default implementation reinstalls the icon in the system tray. You can derive a new class and override this behavior if you want to do something else. The actual WindowProc is a little more complicated than I've shown here since it also processes tray notifications to do all the magic default menu processing. This is another enhancement; previously, you had to call OnTrayNotification yourself when you got WM_MY_TRAY_NOTIFICATION. Figure 4 shows the source code for the new CTrayIcon.

     Incidentally, in case you're wondering how I tested the automatic icon recreation feature (and how you can, too), it's easy. From Windows 98, press Ctrl+Alt+Del to bring up the task list. Select Explorer, and then End Task. You'll get the Shutdown/Restart dialog, in which you should press Cancel (don't shut down!). Then, wait a few seconds, and you'll get a "This task is not responding" message, to which you should reply End Task. Bye-bye Windows Explorer! But-lo and behold!-like the Phoenix, it rises again, and broadcasts TaskbarCreated to all top-level windows. If you're using Windows NT® 4.0 with Internet Explorer 4.0 installed, do the equivalent thing. Once the taskbar is dead, type "explorer" from a command line to restart it. In either case, if the revised TRAYTEST program (see Figure 5) was running before you murdered the taskbar, it automagically reinstalls its icon when Explorer resurrects itself. Go ahead, try it-it really works! Try it with other tray apps, too. You'll find out who knows about TaskbarCreated and who doesn't.

    

Figure 5 TrayIcon

     Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com.

From the February 1999 issue of Microsoft Systems Journal.