Figure 3   CMainFrame

MainFrm.h


////////////////////////////////////////////////////////////////
// GRAYSUB 1998 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//

////////////////
// Standard MFC main frame window
//
class CMainFrame : public CFrameWnd {
public:
    virtual ~CMainFrame();
protected:
    BOOL m_bEnableSubmenu;
    BOOL m_bEnableFoo;

    CMainFrame();
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnEnableSubmenu();
    afx_msg void OnUpdateEnableSubmenu(CCmdUI *pCmdUI);
    afx_msg void OnFoo();
    afx_msg void OnUpdateFoo(CCmdUI *pCmdUI);
    afx_msg void OnEnableFoo();
    afx_msg void OnUpdateEnableFoo(CCmdUI *pCmdUI);
    DECLARE_MESSAGE_MAP()
    DECLARE_DYNCREATE(CMainFrame)
};
MainFrm.cpp

////////////////////////////////////////////////////////////////
// GRAYSUB 1998 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// GRAYSUB illustrates how to gray a submenu item. 
// Compiles with Visual C++ 5.0 or later, under Windows 95.
//
#include "StdAfx.h"
#include "GraySub.h"
#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_CREATE()
    ON_COMMAND(ID_ENABLE_SUBMENU, OnEnableSubmenu)
    ON_UPDATE_COMMAND_UI(ID_ENABLE_SUBMENU, OnUpdateEnableSubmenu)
    ON_COMMAND(ID_FOO, OnFoo)
    ON_UPDATE_COMMAND_UI(ID_FOO, OnUpdateFoo)
    ON_COMMAND(ID_ENABLE_FOO, OnEnableFoo)
    ON_UPDATE_COMMAND_UI(ID_ENABLE_FOO, OnUpdateEnableFoo)
END_MESSAGE_MAP()

CMainFrame::CMainFrame()
{
    m_bEnableSubmenu = FALSE;
    m_bEnableFoo    = FALSE;
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    return 0;
}

//////////////////
// Set initial window size to 200 x 200
//
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    BOOL bRet = CFrameWnd::PreCreateWindow(cs);
    cs.cx = cs.cy = 200;
    return bRet;
}

//////////////////
// Enable/Disable the submenu
//
void CMainFrame::OnEnableSubmenu()
{
    m_bEnableSubmenu = !m_bEnableSubmenu;
}

//////////////////
// Set text for File | Enable/Disable Submenu
//
void CMainFrame::OnUpdateEnableSubmenu(CCmdUI *pCmdUI)
{
    pCmdUI->SetText(m_bEnableSubmenu ?
        _T("&Disable Submenu") : _T("&Enable Submenu"));
}

//////////////////
// Handle File | Submenu | Foo
//
void CMainFrame::OnFoo()
{
    AfxMessageBox(_T("Foo to you too."));
}

//////////////////
// Update handler for File | Submenu | Foo handles updating
// of the command and the submenu item itself.
//
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 command item (File | Submenu | Foo)
        pCmdUI->Enable(m_bEnableFoo);
}

/////////////////
// Enable/Disable the File | Submenu | Foo command
//
void CMainFrame::OnEnableFoo()
{
    m_bEnableFoo = !m_bEnableFoo;
}

/////////////////
// Set text for File | Submenu | Enable/Disable Foo
//
void CMainFrame::OnUpdateEnableFoo(CCmdUI *pCmdUI)
{
    pCmdUI->SetText(m_bEnableFoo ?
        _T("&Disable Foo") : _T("&Enable Foo"));
}

Figure 4   CTrayIcon

TrayIcon.h


////////////////////////////////////////////////////////////////
// CTrayIcon 1999 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.

#ifndef _TRAYICON_H
#define _TRAYICON_H

#include "Subclass.h"

////////////////
// CTrayIcon manages an icon in the Windows 95 system tray. 
// The sample program TRAYTEST shows how to use it.
// 
class CTrayIcon : public CCmdTarget {
public:
   CTrayIcon(UINT uID);
   ~CTrayIcon();

   // Call this to receive tray notifications
   void SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg);

   // SetIcon functions. To remove icon, call SetIcon(0)
   //
   BOOL SetIcon(UINT uID); // main variant you want to use
   BOOL SetIcon(HICON hicon, LPCTSTR lpTip);
   BOOL SetIcon(LPCTSTR lpResName, LPCTSTR lpTip)
      { return SetIcon(lpResName ? 
         AfxGetApp()->LoadIcon(lpResName) : NULL, lpTip); }
   BOOL SetStandardIcon(LPCTSTR lpszIconName, LPCTSTR lpTip)
      { return SetIcon(::LoadIcon(NULL, lpszIconName), lpTip); }

// Following is obsolete. CTrayIcon does default handling automatically.
// virtual LRESULT OnTrayNotification(WPARAM uID, LPARAM lEvent);
   virtual LRESULT OnTrayNotify(WPARAM uID, LPARAM lEvent);
   virtual LRESULT OnTaskBarCreate(WPARAM wp, LPARAM lp);

protected:
   NOTIFYICONDATA m_nid;        // struct for Shell_NotifyIcon args

   // private class used to hook tray notification and taskbarcreated
   class CTrayHook : public CSubclassWnd {
   private:
      CTrayIcon* m_pTrayIcon;
      virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
      friend CTrayIcon;
   };
   friend CTrayHook;
   CTrayHook m_notifyHook; // trap tray notifications
   CTrayHook m_parentHook; // trap taskbarcreated message
   DECLARE_DYNAMIC(CTrayIcon)
};

#endif
TrayIcon.cpp

//////////////////////////////////////////////////////////////////
// 1999 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
#include "stdafx.h"
#include "trayicon.h"
#include <afxpriv.h>    // for AfxLoadString

#define countof(x)    (sizeof(x)/sizeof(x[0]))

//////////////////
// Windows sends this message when the taskbar is created. This can happen
// if it crashes and Windows has to restart it. CTrayIcon responds by
// re-installing its icon.
//
const UINT WM_TASKBARCREATED = ::RegisterWindowMessage(_T("TaskbarCreated"));

IMPLEMENT_DYNAMIC(CTrayIcon, CCmdTarget)

CTrayIcon::CTrayIcon(UINT uID)
{
    // Initialize NOTIFYICONDATA
    memset(&m_nid, 0 , sizeof(m_nid));
    m_nid.cbSize = sizeof(m_nid);
    m_nid.uID = uID;  // never changes after construction

    m_notifyHook.m_pTrayIcon = this; // notification window hook
    m_parentHook.m_pTrayIcon = this; // parent window hook

    // Use resource string as tip if there is one
    AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
}

CTrayIcon::~CTrayIcon()
{
    SetIcon(0); // remove icon from system tray
}

//////////////////
// Set notification window. It must be created already.
//
void CTrayIcon::SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg)
{
    // If the following assert fails, you're probably
    // calling me before you created your window. Oops.
    ASSERT(pNotifyWnd==NULL || ::IsWindow(pNotifyWnd->GetSafeHwnd()));
    m_nid.hWnd = pNotifyWnd->GetSafeHwnd();

    ASSERT(uCbMsg==0 || uCbMsg>=WM_USER);
    m_nid.uCallbackMessage = uCbMsg;

    CWnd* pParentWnd = pNotifyWnd ? pNotifyWnd->GetTopLevelParent() : NULL;

    // Install window hooks. Must be different because
    // taskbar creation message only goes to top-level parent.
    m_notifyHook.HookWindow(pNotifyWnd);
    if (pParentWnd!=pNotifyWnd)
        m_parentHook.HookWindow(pParentWnd);
}

//////////////////
// This is the main variant for setting the icon.
// Sets both the icon and tooltip from resource ID
// To remove the icon, call SetIcon(0)
//
BOOL CTrayIcon::SetIcon(UINT uID)
{ 
    HICON hicon=NULL;
    if (uID) {
        AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
        hicon = AfxGetApp()->LoadIcon(uID);
    }
    return SetIcon(hicon, NULL);
}

//////////////////
// Common SetIcon for all overloads. 
//
BOOL CTrayIcon::SetIcon(HICON hicon, LPCTSTR lpTip) 
{
    UINT msg;
    m_nid.uFlags = 0;

    // Set the icon
    if (hicon) {
        // Add or replace icon in system tray
        msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD;
        m_nid.hIcon = hicon;
        m_nid.uFlags |= NIF_ICON;
    } else { // remove icon from tray
        if (m_nid.hIcon==NULL)
            return TRUE;        // already deleted
        msg = NIM_DELETE;
    }

    // Use the tip, if any
    if (lpTip)
        _tcsncpy(m_nid.szTip, lpTip, countof(m_nid.szTip));
    if (m_nid.szTip[0])
        m_nid.uFlags |= NIF_TIP;

    // Use callback if any
    if (m_nid.uCallbackMessage && m_nid.hWnd)
        m_nid.uFlags |= NIF_MESSAGE;

    // Do it
    BOOL bRet = Shell_NotifyIcon(msg, &m_nid);
    if (msg==NIM_DELETE || !bRet)
        m_nid.hIcon = NULL;  // failed
    return bRet;
}

//////////////////
// Same hook class used for both notification window and top-level
// parent; hook function determines which.
//
LRESULT CTrayIcon::CTrayHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
    if (msg==m_pTrayIcon->m_nid.uCallbackMessage &&
        m_hWnd==m_pTrayIcon->m_nid.hWnd) {

        m_pTrayIcon->OnTrayNotify(wp, lp);

    } else if (msg==WM_TASKBARCREATED) {
        m_pTrayIcon->OnTaskBarCreate(wp, lp);
    }
    return CSubclassWnd::WindowProc(msg, wp, lp);
}

/////////////////
// Default event handler handles right-menu and doubleclick.
// Override to do something different.
//
LRESULT CTrayIcon::OnTrayNotify(WPARAM wID, LPARAM lEvent)
{
    if (wID!=m_nid.uID ||
        (lEvent!=WM_RBUTTONUP && lEvent!=WM_LBUTTONDBLCLK))
        return 0;

    // If there's a resource menu with the same ID as the icon, use it as 
    // the right-button popup menu. CTrayIcon will interpret the first
    // item in the menu as the default command for WM_LBUTTONDBLCLK
    // 
    CMenu menu;
    if (!menu.LoadMenu(m_nid.uID))
        return 0;
    CMenu* pSubMenu = menu.GetSubMenu(0);
    if (!pSubMenu) 
        return 0;

    if (lEvent==WM_RBUTTONUP) {

        // Make first menu item the default (bold font)
        ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);

        // Display the menu at the current mouse location. There's a "bug"
        // in Windows 95 that requires calling SetForegroundWindow. 
        // To find out more, search for Q135788 in MSDN.
        //
        CPoint mouse;
        GetCursorPos(&mouse);
        ::SetForegroundWindow(m_nid.hWnd);  
        ::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0,
            m_nid.hWnd, NULL);

    } else  // double click: execute first menu item
        ::SendMessage(m_nid.hWnd, WM_COMMAND, pSubMenu->GetMenuItemID(0), 0);

    return 1; // handled
}

//////////////////
// Explorer had to restart the taskbar: add icons again
//
LRESULT CTrayIcon::OnTaskBarCreate(WPARAM wp, LPARAM lp)
{
    // Reinstall taskbar icon
    HICON hIcon = m_nid.hIcon;
    m_nid.hIcon = NULL;
    if (hIcon)
        SetIcon(hIcon, NULL); // will reuse current tip
    return 0;
}

Figure 6   CMenuBar

MenuBar.h


 // 1998 Paul DiLascia
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // CMenuBar uses this private class to intercept messages on behalf
 // of its owning frame.
 //
 class CMenuBarFrameHook : public CSubclassWnd {
 protected:
    friend class CMenuBar;
    CMenuBar* m_pMenuBar;
    CMenuBarFrameHook();
    ~CMenuBarFrameHook();
    BOOL Install(CMenuBar* pMenuBar, HWND hWndToHook);
    virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
 };
 
 // CMenuBar implements an Office 97-style menu bar. Use it the way you would
 // a CToolBar, only you need not call LoadToolBar. All you have to do is
 // * Create the CMenuBar from your OnCreate or OnCreateBands handler.
 // * Call LoadMenu to load a menu. This will set your frame's menu to NULL.
 // * Implemenent your frame's PreTranslateMessage function, to call
 //   CMenuBar::TranslateFrameMessage. 
 //
 class CMenuBar : public CFlatToolBar {
 public:
    BOOL   m_bAutoRemoveFrameMenu;       // set frame's menu to NULL
    CMenuBar();
    ~CMenuBar() { }
 
    // You must call this from your frame's PreTranslateMessage fn
    virtual BOOL TranslateFrameMessage(MSG* pMsg);
 
    HMENU LoadMenu(HMENU hmenu, HMENU hmenuWindow); // load menu
    HMENU LoadMenu(LPCSTR lpszMenuName);            // ...from resource file
    HMENU LoadMenu(UINT nID) {
       return LoadMenu(MAKEINTRESOURCE(nID));
    }
    HMENU GetMenu() { return m_hmenu; }             // get current menu
 
    enum TRACKINGSTATE { // menubar has three states:
       TRACK_NONE = 0,   // * normal, not tracking anything
       TRACK_BUTTON,     // * tracking buttons (F10/Alt mode)
       TRACK_POPUP       // * tracking popups
    };
    static BOOL bTRACE;                  // set TRUE to see TRACE msgs
 
 protected:
    CMenuBarFrameHook m_frameHook;       // hooks frame window messages
    CStringArray      m_arStrings;       // array of menu item names
    HMENU             m_hmenu;           // the menu
 
    // menu tracking stuff:
    int    m_iPopupTracking;             // which popup I'm tracking if any
    int    m_iNewPopup;                  // next menu to track
    BOOL   m_bProcessRightArrow;         // process l/r arrow keys?
    BOOL   m_bProcessLeftArrow;          // ...
    BOOL   m_bEscapeWasPressed;          // user pressed escape to exit menu
    CPoint m_ptMouse;                    // mouse location when tracking popup
    HMENU  m_hMenuTracking;              // current popup I'm tracking
 
    TRACKINGSTATE m_iTrackingState;      // current tracking state
 
    // helpers
    void  RecomputeToolbarSize();
    void  RecomputeMenuLayout();
    int   GetNextOrPrevButton(int iButton, BOOL bPrev);
    void  SetTrackingState(TRACKINGSTATE iState, int iButton=-1);
    void  TrackPopup(int iButton);
    void  ToggleTrackButtonMode();
    void  CancelMenuAndTrackNewOne(int iButton);
    void  OnMenuSelect(HMENU hmenu, UINT nItemID);
    CPoint ComputeMenuTrackPoint(const CRect& rcButn, TPMPARAMS& tpm);
 
    BOOL  IsValidButton(int iButton) const
       { return 0 <= iButton && iButton < GetButtonCount(); }
 
    virtual BOOL OnMenuInput(MSG& m);    // handle popup menu input
 
    // overrides
    int HitTest(CPoint p) const;
 
    // command/message handlers
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint pt);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnUpdateMenuButton(CCmdUI* pCmdUI);
    afx_msg LRESULT OnSetMenuNull(WPARAM wp, LPARAM lp);
 
    static LRESULT CALLBACK MenuInputFilter(int code, WPARAM wp, LPARAM lp);
    
    DECLARE_DYNAMIC(CMenuBar)
    DECLARE_MESSAGE_MAP()
 };

MenuBar.cpp


 // CMenuBar implements menu bar for MFC. See MenuBar.h for how
 // to use, and also the MBTest sample application.
 //
 #include "StdAfx.h"
 #include "MenuBar.h"
 
 const UINT MB_SET_MENU_NULL = WM_USER + 1100;
 
 IMPLEMENT_DYNAMIC(CMenuBar, CFlatToolBar)
 BEGIN_MESSAGE_MAP(CMenuBar, CFlatToolBar)
    ON_WM_CREATE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MOUSEMOVE()
    ON_WM_SIZE()
    ON_UPDATE_COMMAND_UI_RANGE(0, 256, OnUpdateMenuButton)
    ON_MESSAGE(MB_SET_MENU_NULL, OnSetMenuNull)
 END_MESSAGE_MAP()
 
 CMenuBar::CMenuBar()
 {
    m_iTrackingState = TRACK_NONE;       // initial state: not tracking 
    m_iPopupTracking = m_iNewPopup = -1; // invalid
    m_hmenu = NULL;
    m_bAutoRemoveFrameMenu = TRUE;       // set frame's menu to NULL
 }
 
 int CMenuBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
 {
    VERIFY(CFlatToolBar::OnCreate(lpCreateStruct)==0);
    CWnd* pFrame = GetOwner();
    m_frameHook.Install(this, pFrame->GetSafeHwnd());
    return 0; // OK
 }
 
 // The reason for having this is so MFC won't automatically disable
 // the menu buttons. Assumes < 256 top-level menu items. The ID of
 // the ith menu button is i. IOW, the index and ID are the same.
 //
 void CMenuBar::OnUpdateMenuButton(CCmdUI* pCmdUI)
 {
    ASSERT_VALID(this);
    if (IsValidButton(pCmdUI->m_nID))
       pCmdUI->Enable(TRUE);
 }
 
 // Recompute layout of menu bar
 //
 void CMenuBar::RecomputeMenuLayout()
 {
    SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
       SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
 }
 
 // Make frame recalculate control bar sizes after menu change
 //
 void CMenuBar::RecomputeToolbarSize()
 {
    // Force toolbar to recompute size
    CFrameWnd* pFrame = (CFrameWnd*)GetOwner();
    ASSERT_VALID(pFrame);
    ASSERT(pFrame->IsFrameWnd());
    pFrame->RecalcLayout();
 
    // floating frame
    pFrame = GetParentFrame();
    if (pFrame->IsKindOf(RUNTIME_CLASS(CMiniFrameWnd)))
       pFrame->RecalcLayout(); 
 }
 
 // Set tracking state: none, button, or popup
 //
 void CMenuBar::SetTrackingState(TRACKINGSTATE iState, int iButton)
 {
    ASSERT_VALID(this);
    if (iState != m_iTrackingState) {
       if (iState == TRACK_NONE)
          iButton = -1;
 
       SetHotItem(iButton);              // could be none (-1)
 
       if (iState==TRACK_POPUP) {
          // set related state stuff
          m_bEscapeWasPressed = FALSE;   // assume Esc key not pressed
          m_bProcessRightArrow =         // assume left/right arrow..
             m_bProcessLeftArrow = TRUE; // ..will move to prev/next popup
          m_iPopupTracking = iButton;    // which popup I'm tracking
       }
       m_iTrackingState = iState;
    }
 }
 
 // Toggle state from home state to button-tracking and back
 //
 void CMenuBar::ToggleTrackButtonMode()
 {
    ASSERT_VALID(this);
    if (m_iTrackingState == TRACK_NONE || m_iTrackingState == TRACK_BUTTON) {
       SetTrackingState(m_iTrackingState == TRACK_NONE ?
          TRACK_BUTTON : TRACK_NONE, 0);
    }
 }
 
 // Get button index before/after a given button
 //
 int CMenuBar::GetNextOrPrevButton(int iButton, BOOL bPrev)
 {
    ASSERT_VALID(this);
    if (bPrev) {
       iButton--;
       if (iButton <0)
          iButton = GetButtonCount() - 1;
    } else {
       iButton++;
       if (iButton >= GetButtonCount())
          iButton = 0;
    }
    return iButton;
 }
 
 // This is to correct a bug in the system toolbar control: TB_HITTEST only
 // looks at the buttons, not the size of the window. So it returns a button
 // hit even if that button is totally outside the size of the window!
 //
 int CMenuBar::HitTest(CPoint p) const
 {
    int iHit = CFlatToolBar::HitTest(p);
    if (iHit>0) {
       CRect rc;
       GetClientRect(&rc);
       if (!rc.PtInRect(p)) // if point is outside window
          iHit = -1;        // can't be a hit!
    }
    return iHit;
 }
 
 // Load a different menu. The HMENU must not belong to any CMenu,
 // and you must free it when you're done. Returns old menu
 // hMenuWindow is MDI "Window" menu, if any
 //
 HMENU CMenuBar::LoadMenu(HMENU hmenu, HMENU hmenuWindow)
 {
    MBTRACEFN(_T("CMenuBar::LoadMenu\n"));
    UINT iPrevID=(UINT)-1;
    ASSERT(::IsMenu(hmenu));
    ASSERT_VALID(this);
 
    if (m_bAutoRemoveFrameMenu) {
       CFrameWnd* pFrame = GetParentFrame();
       if (::GetMenu(*pFrame)!=NULL) {
          // I would like to set the frame's menu to NULL now, but if I do, MFC
          // gets all upset: it calls GetMenu and expects to have a real menu.
          // So Instead, I post a message to myself. Because the message is
          // posted, not sent, I won't process it until MFC is done with all its
          // initialization stuff. (MFC needs to set CFrameWnd::m_hMenuDefault
          // to the menu, which it gets by calling GetMenu.)
          //
          PostMessage(MB_SET_MENU_NULL, (WPARAM)pFrame->GetSafeHwnd());
       }
    }
    HMENU hOldMenu = m_hmenu;
    m_hmenu = hmenu;
 
    // delete existing buttons
    int nCount = GetButtonCount();
    while (nCount--) {
       VERIFY(DeleteButton(0));
    }
    SetImageList(NULL);
 // SetButtonSize(CSize(0,0)); // This barfs in VC 6.0
 
    DWORD dwStyle = GetStyle();
    BOOL bModifyStyle = ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT);
 
    // add text buttons
    UINT nMenuItems = hmenu ? ::GetMenuItemCount(hmenu) : 0;
    for (UINT i=0; i < nMenuItems; i++) {
       TCHAR name[64];
       memset(name, 0, sizeof(name)); // guarantees double-0 at end
       if (::GetMenuString(hmenu, i, name, countof(name)-1, MF_BYPOSITION)) {
          TBBUTTON tbb;
          memset(&tbb, 0, sizeof(tbb));
          tbb.idCommand = ::GetMenuItemID(hmenu, i);
 
          // Because the toolbar is too brain-damaged to know if it already has
          // a string, and is also too brain-dead to even let you delete strings,
          // I have to determine if each string has been added already. Otherwise
          // in a MDI app, as the menus are repeatedly switched between doc and
          // no-doc menus, I will keep adding strings until somebody runs out of
          // memory. Sheesh!
          // 
          int iString = -1;
          for (int j=0; j<m_arStrings.GetSize(); j++) {
             if (m_arStrings[j] == name) {
                iString = j; // found it
                break;
             }
          }
          if (iString <0) {
             // string not found: add it
             iString = AddStrings(name);
             m_arStrings.SetAtGrow(iString, name);
          }
          tbb.iString = iString;
          tbb.fsState = TBSTATE_ENABLED;
          tbb.fsStyle = TBSTYLE_AUTOSIZE;
          tbb.iBitmap = -1;
          tbb.idCommand = i;
          VERIFY(AddButtons(1, &tbb));
       }
    }
 
    if (bModifyStyle)
       SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
    
    if (hmenu) {
       AutoSize();                       // size buttons
       RecomputeToolbarSize();           // and menubar itself
    }
    return hOldMenu;
 }
 
 // Load menu from resource
 //
 HMENU CMenuBar::LoadMenu(LPCSTR lpszMenuName)
 {
    return LoadMenu(::LoadMenu(AfxGetResourceHandle(), lpszMenuName), NULL);
 }
 
 // Set the frame's menu to NULL. WPARAM is HWND of frame.
 //
 LRESULT CMenuBar::OnSetMenuNull(WPARAM wp, LPARAM lp)
 {
    HWND hwnd = (HWND)wp;
    ASSERT(::IsWindow(hwnd));
    ::SetMenu(hwnd, NULL);
    return 0;
 }
 
 // Handle mouse click: if clicked on button, press it
 // and go into main menu loop.
 //
 void CMenuBar::OnLButtonDown(UINT nFlags, CPoint pt)
 {
    MBTRACEFN(_T("CMenuBar::OnLButtonDown\n"));
    ASSERT_VALID(this);
    int iButton = HitTest(pt);
    if (iButton >= 0 && iButton<GetButtonCount()) // if mouse is over a button:
       TrackPopup(iButton);                       //   track it
    else                                          // otherwise:
       CFlatToolBar::OnLButtonDown(nFlags, pt);   //   pass it on...
 }
 
 void CMenuBar::OnLButtonUp(UINT nFlags, CPoint pt)
 {
 }
 
 // Handle mouse movement
 //
 void CMenuBar::OnMouseMove(UINT nFlags, CPoint pt)
 {
    ASSERT_VALID(this);
 
    if (m_iTrackingState==TRACK_BUTTON) {
 
       // In button-tracking state, ignore mouse-over to non-button area.
       // Normally, the toolbar would de-select the hot item in this case.
       // Only change the hot item if the mouse has actually moved.
       // This is necessary to avoid a bug where the user moves to a different
       // button from the one the mouse is over, and presses arrow-down to get
       // the menu, then Esc to cancel it. Without this code, the button will
       // jump to wherever the mouse is--not right.
 
       int iHot = HitTest(pt);
       if (IsValidButton(iHot) && pt != m_ptMouse)
          SetHotItem(iHot);
       return;         // don't let toolbar get it
    }
    m_ptMouse = pt; // remember point
    CFlatToolBar::OnMouseMove(nFlags, pt);
 }
 
 // Window was resized: need to recompute layout
 //
 void CMenuBar::OnSize(UINT nType, int cx, int cy)
 {
    CFlatToolBar::OnSize(nType, cx, cy);
    RecomputeMenuLayout();
 }
 
 // when user selects a new menu item, note whether it has a submenu
 // and/or parent menu, so I know whether right/left arrow should
 // move to the next popup.
 //
 void CMenuBar::OnMenuSelect(HMENU hmenu, UINT iItem)
 {
    if (m_iTrackingState > 0) {
       // process right-arrow iff item is NOT a submenu
       m_bProcessRightArrow = (::GetSubMenu(hmenu, iItem) == NULL);
       // process left-arrow iff curent menu is one I'm tracking
       m_bProcessLeftArrow = hmenu==m_hMenuTracking;
    }
 }
 
 // globals--yuk! But no other way using windows hooks.
 //
 static CMenuBar*  g_pMenuBar = NULL;
 static HHOOK      g_hMsgHook = NULL;
 
 // Menu filter hook just passes to virtual CMenuBar function
 //
 LRESULT CALLBACK
 CMenuBar::MenuInputFilter(int code, WPARAM wp, LPARAM lp)
 {
    return (code==MSGF_MENU && g_pMenuBar &&
       g_pMenuBar->OnMenuInput(*((MSG*)lp))) ? TRUE
       : CallNextHookEx(g_hMsgHook, code, wp, lp);
 }
 
 // Handle menu input event: Look for left/right to change popup menu,
 // mouse movement over a different menu button for "hot" popup effect.
 // Returns TRUE if message handled (to eat it).
 //
 BOOL CMenuBar::OnMenuInput(MSG& m)
 {
    ASSERT_VALID(this);
    ASSERT(m_iTrackingState == TRACK_POPUP); // sanity check
    int msg = m.message;
 
    if (msg==WM_KEYDOWN) {
       // handle left/right-arow.
       TCHAR vkey = m.wParam;
       if ((vkey == VK_LEFT  && m_bProcessLeftArrow) ||
          (vkey == VK_RIGHT && m_bProcessRightArrow)) {
 
          MBTRACE(_T("CMenuBar::OnMenuInput: handle VK_LEFT/RIGHT\n"));
          CancelMenuAndTrackNewOne(
             GetNextOrPrevButton(m_iPopupTracking, vkey==VK_LEFT));
          return TRUE; // eat it
 
       } else if (vkey == VK_ESCAPE) {
          m_bEscapeWasPressed = TRUE;    // (menu will abort itself)
       }
 
    } else if (msg==WM_MOUSEMOVE || msg==WM_LBUTTONDOWN) {
       // handle mouse move or click
       CPoint pt = m.lParam;
       ScreenToClient(&pt);
 
       if (msg == WM_MOUSEMOVE) {
          if (pt != m_ptMouse) {
             int iButton = HitTest(pt);
             if (IsValidButton(iButton) && iButton != m_iPopupTracking) {
                // user moved mouse over a different button: track its popup
                CancelMenuAndTrackNewOne(iButton);
             }
             m_ptMouse = pt;
          }
 
       } else if (msg == WM_LBUTTONDOWN) {
          if (HitTest(pt) == m_iPopupTracking) {
             // user clicked on same button I am tracking: cancel menu
             MBTRACE(_T("CMenuBar:OnMenuInput: 
                        handle mouse click to exit popup\n"));
             CancelMenuAndTrackNewOne(-1);
             return TRUE; // eat it
          }
       }
    }
    return FALSE; // not handled
 }
 
 // Cancel the current popup menu by posting WM_CANCELMODE, and track a new
 // menu. iNewPopup is which new popup to track (-1 to quit).
 //
 void CMenuBar::CancelMenuAndTrackNewOne(int iNewPopup)
 {
    MBTRACE(_T("CMenuBar::CancelMenuAndTrackNewOne: %d\n"), iNewPopup);
    ASSERT_VALID(this);
    if (iNewPopup != m_iPopupTracking) {
       GetOwner()->PostMessage(WM_CANCELMODE); // quit menu loop
       m_iNewPopup = iNewPopup;                // go to this popup (-1 = quit)
    }
 }
 
 // Track the popup submenu associated with the i'th button in the menu bar.
 // This fn actually goes into a loop, tracking different menus until the user
 // selects a command or exits the menu.
 //
 void CMenuBar::TrackPopup(int iButton)
 {
    MBTRACE(_T("CMenuBar::TrackPopup %d\n"), iButton);
    ASSERT_VALID(this);
    ASSERT(m_hmenu);
 
    CMenu menu;
    menu.Attach(m_hmenu);
    int nMenuItems = menu.GetMenuItemCount();
 
    while (iButton >= 0) {               // while user selects another menu
 
       m_iNewPopup = -1;                 // assume quit after this
       PressButton(iButton, TRUE);       // press the button
       UpdateWindow();                   // and force repaint now
 
       // post a simulated arrow-down into the message stream
       // so TrackPopupMenu will read it and move to the first item
       GetOwner()->PostMessage(WM_KEYDOWN, VK_DOWN, 1);
       GetOwner()->PostMessage(WM_KEYUP, VK_DOWN, 1);
 
       SetTrackingState(TRACK_POPUP, iButton); // enter tracking state
 
       // Need to install a hook to trap menu input in order to make
       // left/right-arrow keys and "hot" mouse tracking work.
       //
       ASSERT(g_pMenuBar == NULL);
       g_pMenuBar = this;
       ASSERT(g_hMsgHook == NULL);
       g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,
          MenuInputFilter, NULL, ::GetCurrentThreadId());
 
       // get submenu and display it beneath button
       TPMPARAMS tpm;
       CRect rcButton;
       GetRect(iButton, rcButton);
       ClientToScreen(&rcButton);
       CPoint pt = ComputeMenuTrackPoint(rcButton, tpm);
       HMENU hMenuPopup = ::GetSubMenu(m_hmenu, iButton);
       ASSERT(hMenuPopup);
       BOOL bRet = TrackPopupMenuEx(hMenuPopup,
          TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_VERTICAL,
          pt.x, pt.y, GetOwner()->GetSafeHwnd(), &tpm);
 
       // uninstall hook, etc.
       ::UnhookWindowsHookEx(g_hMsgHook);
       g_hMsgHook = NULL;
       g_pMenuBar = NULL;
 
       PressButton(iButton, FALSE);   // un-press button
       UpdateWindow();                // and force repaint now
 
       // If the user exited the menu loop by pressing Escape,
       // return to track-button state; otherwise normal non-tracking state.
       SetTrackingState(m_bEscapeWasPressed ?
          TRACK_BUTTON : TRACK_NONE, iButton);
 
       // If the user moved mouse to a new top-level popup (eg from File to
       // Edit button), I will have posted a WM_CANCELMODE to quit
       // the first popup, and set m_iNewPopup to the new menu to show.
       // Otherwise, m_iNewPopup will be -1 as set above.
       // So just set iButton to the next popup menu and keep looping...
       iButton = m_iNewPopup;
    }
    menu.Detach();
 }
 
 // This function translates special menu keys and mouse actions.
 // You must call it from your frame's PreTranslateMessage.
 //
 BOOL CMenuBar::TranslateFrameMessage(MSG* pMsg)
 {
    ASSERT_VALID(this);
    ASSERT(pMsg);
    UINT msg = pMsg->message;
    if (WM_LBUTTONDOWN <= msg && msg <= WM_MOUSELAST) {
       if (pMsg->hwnd != m_hWnd && m_iTrackingState > 0) {
          // user clicked outside menu bar: exit tracking mode
          MBTRACE(_T("CMenuBar::TranslateFrameMessage: 
                     user clicked outside menu bar: end tracking\n"));
          SetTrackingState(TRACK_NONE);
       }
 
    } else if (msg==WM_SYSKEYDOWN || msg==WM_SYSKEYUP || msg==WM_KEYDOWN) {
 
       BOOL bAlt = HIWORD(pMsg->lParam) & KF_ALTDOWN; // Alt key down
       TCHAR vkey = pMsg->wParam;                     // get virt key
       if (vkey==VK_MENU ||
          (vkey==VK_F10 && !((GetKeyState(VK_SHIFT) & 0x80000000) ||
                             (GetKeyState(VK_CONTROL) & 0x80000000) || bAlt))) {
 
          // key is VK_MENU or F10 with no alt/ctrl/shift: toggle menu mode
          if (msg==WM_SYSKEYUP) {
             MBTRACE(_T("CMenuBar::TranslateFrameMessage: handle menu key\n"));
             ToggleTrackButtonMode();
          }
          return TRUE;
 
       } else if ((msg==WM_SYSKEYDOWN || msg==WM_KEYDOWN)) {
          if (m_iTrackingState == TRACK_BUTTON) {
             // I am tracking: handle left/right/up/down/space/Esc
             switch (vkey) {
             case VK_LEFT:
             case VK_RIGHT:
                // left or right-arrow: change hot button if tracking buttons
                MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_LEFT/RIGHT\n"));
                SetHotItem(GetNextOrPrevButton(GetHotItem(), vkey==VK_LEFT));
                return TRUE;
 
             case VK_SPACE:  // (personally, I like SPACE to enter menu too)
             case VK_UP:
             case VK_DOWN:
                // up or down-arrow: move into current menu, if any
                MBTRACE(_T("CMenuBar::TranslateFrameMessage: 
                           VK_UP/DOWN/SPACE\n"));
                TrackPopup(GetHotItem());
                return TRUE;
 
             case VK_ESCAPE:
                // escape key: exit tracking mode
                MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_ESCAPE\n"));
                SetTrackingState(TRACK_NONE);
                return TRUE;
             }
          }
 
          // Handle alphanumeric key: invoke menu. Note that Alt-X
          // chars come through as WM_SYSKEYDOWN, plain X as WM_KEYDOWN.
          if ((bAlt || m_iTrackingState == TRACK_BUTTON) && isalnum(vkey)) {
             // Alt-X, or else X while in tracking mode
             UINT nID;
             if (MapAccelerator(vkey, nID)) {
                MBTRACE(_T("CMenuBar::TranslateFrameMessage: map acclerator\n"));
                TrackPopup(nID);   // found menu mnemonic: track it
                return TRUE;       // handled
             } else if (m_iTrackingState==TRACK_BUTTON && !bAlt) {
                MessageBeep(0);
                return TRUE;
             }
          }
 
          // Default for any key not handled so far: return to no-menu state
          if (m_iTrackingState > 0) {
             MBTRACE(_T("CMenuBar::TranslateFrameMessage: unknown key, 
                        stop tracking\n"));
             SetTrackingState(TRACK_NONE);
          }
       }
    }
    return FALSE; // not handled, pass along
 }
 
 // CMenuBarFrameHook is used to trap menu-related messages sent to the owning
 // frame. The same class is also used to trap messages sent to the MDI client
 // window in an MDI app. I should really use two classes for this,
 // but it uses less code to char the same class. Note however: there
 // are two different INSTANCES of CMenuBarFrameHook in CMenuBar: one for
 // the frame and one for the MDI client window.
 //
 CMenuBarFrameHook::CMenuBarFrameHook()
 {
 }
 
 CMenuBarFrameHook::~CMenuBarFrameHook()
 {
    HookWindow((HWND)NULL); // (unhook)
 }
 
 // Install hook to trap window messages sent to frame or MDI client.
 // 
 BOOL CMenuBarFrameHook::Install(CMenuBar* pMenuBar, HWND hWndToHook)
 {
    ASSERT_VALID(pMenuBar);
    m_pMenuBar = pMenuBar;
    return HookWindow(hWndToHook);
 }
 
 // Trap frame/MDI client messages specific to menubar. 
 //
 LRESULT CMenuBarFrameHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
 {
    CMenuBar& mb = *m_pMenuBar;
 
    switch (msg) {
    case WM_MENUSELECT:
       mb.OnMenuSelect((HMENU)lp, (UINT)LOWORD(wp));
       break;
    }
    return CSubclassWnd::WindowProc(msg, wp, lp);
 }