Figure 4   LoadMenu


 HMENU CMenuBar::LoadMenu(HMENU hmenu)
 {
   m_hmenu = hmenu;
   // omitted for your protection
 •
 •
 •
  // delete existing buttons
   int nCount = GetButtonCount();
   while (nCount--) {
     VERIFY(DeleteButton(0));
   }
 
   // now add each toolbar button
   UINT n = ::GetMenuItemCount(hmenu);
   for (UINT i=0; i < n; i++) {
     TBBUTTON tbb;
     tbb.idCommand = ::GetMenuItemID(hmenu,i);
     tbb.iString = // item name, see below
     tbb.fsState = TBSTATE_ENABLED;
     tbb.fsStyle = TBSTYLE_AUTOSIZE;
     tbb.iBitmap = -1; // no icon
     tbb.idCommand = i;
     VERIFY(AddButtons(1, &tbb));
   }
   AutoSize(); // (TB_AUTOSIZE)
 }


Figure 5   TrackPopup


 static CMenuBar* g_pMenuBar bar = NULL;
 static HHOOK     g_hMsgHook = NULL;
 
 void CMenuBar::TrackPopup(int iButton)
 {
   while (iButton >= 0) { // track popup
     PressButton(iButton, TRUE);
 
     m_iNewPopup = -1; // assume no next popup
     m_iTracking = iButton; // popup I'm tracking
 
     // Install hook
     g_pMenuBar = this;
     g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,
       MenuInputFilter, NULL, ::GetCurrentThreadId());
 
     // run popup
     TrackPopupMenuEx(...);
 
     // remove hook
     ::UnhookWindowsHookEx(g_hMsgHook);
 
     PressButton(iButton, FALSE);
     iButton = m_iNewPopup; // track next, if any
   }
 }

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);
 }