UIMenuBar.cpp
上传用户:yatsl7111
上传日期:2007-01-08
资源大小:1433k
文件大小:21k
源码类别:

图形图象

开发平台:

Visual C++

  1. ////////////////////////////////////////////////////////////////
  2. // Copyright 1998 Paul DiLascia
  3. // If this code works, it was written by Paul DiLascia.
  4. // If not, I don't know who wrote it.
  5. //
  6. // CMenuBar implements menu bar for MFC. See MenuBar.h for how
  7. // to use, and also the MBTest sample application.
  8. //
  9. #include "StdAfx.h"
  10. #include "UIMenuBar.h"
  11. const UINT MB_SET_MENU_NULL = WM_USER + 1100;
  12. #ifdef _DEBUG
  13. #define new DEBUG_NEW
  14. #undef THIS_FILE
  15. static char THIS_FILE[] = __FILE__;
  16. #endif
  17. // if you want to see extra TRACE diagnostics, set CMenuBar::bTRACE = TRUE
  18. BOOL CMenuBar::bTRACE = FALSE;
  19. #ifdef _DEBUG
  20. #define MBTRACEFN
  21. CTraceFn __fooble;
  22. if (CMenuBar::bTRACE)
  23. TRACE
  24. #define MBTRACE
  25. if (CMenuBar::bTRACE)
  26. TRACE
  27. #else
  28. #define MBTRACEFN TRACE
  29. #define MBTRACE   TRACE
  30. #endif
  31. IMPLEMENT_DYNAMIC(CMenuBar, CFlatToolBar)
  32. BEGIN_MESSAGE_MAP(CMenuBar, CFlatToolBar)
  33. ON_WM_CREATE()
  34. ON_WM_LBUTTONDOWN()
  35. ON_WM_MOUSEMOVE()
  36. ON_WM_SIZE()
  37. ON_UPDATE_COMMAND_UI_RANGE(0, 256, OnUpdateMenuButton)
  38. ON_MESSAGE(MB_SET_MENU_NULL, OnSetMenuNull)
  39. END_MESSAGE_MAP()
  40. CMenuBar::CMenuBar()
  41. {
  42. if (iVerComCtl32 <= 470)
  43. AfxMessageBox(_T("Warning: This program requires comctl32.dll version 4.71 or greater."));
  44. m_iTrackingState = TRACK_NONE;  // initial state: not tracking 
  45. m_iPopupTracking = m_iNewPopup = -1; // invalid
  46. m_hmenu = NULL;
  47. m_hMenuTracking = NULL;
  48. m_bAutoRemoveFrameMenu = TRUE;  // set frame's menu to NULL
  49. }
  50. CMenuBar::~CMenuBar()
  51. {
  52. }
  53. //////////////////
  54. // Menu bar was created: install hook into owner window
  55. //
  56. int CMenuBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
  57. {
  58. if (CFlatToolBar::OnCreate(lpCreateStruct)==-1)
  59. return -1;
  60. UpdateFont();
  61. CWnd* pFrame = GetOwner();
  62. ASSERT_VALID(pFrame);
  63. m_frameHook.Install(this, *pFrame);
  64. return 0; // OK
  65. }
  66. //////////////////
  67. // Set menu bar font from current system menu font
  68. //
  69. void CMenuBar::UpdateFont()
  70. {
  71. static CFont font;
  72. NONCLIENTMETRICS info;
  73. info.cbSize = sizeof(info);
  74. SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0);
  75. if ((HFONT)font)
  76. font.DeleteObject();
  77. VERIFY(font.CreateFontIndirect(&info.lfMenuFont));
  78. SetFont(&font);
  79. }
  80. //////////////////
  81. // The reason for having this is so MFC won't automatically disable
  82. // the menu buttons. Assumes < 256 top-level menu items. The ID of
  83. // the ith menu button is i. IOW, the index and ID are the same.
  84. //
  85. void CMenuBar::OnUpdateMenuButton(CCmdUI* pCmdUI)
  86. {
  87. ASSERT_VALID(this);
  88. if (IsValidButton(pCmdUI->m_nID))
  89. pCmdUI->Enable(TRUE);
  90. }
  91. //////////////////
  92. // Recompute layout of menu bar
  93. //
  94. void CMenuBar::RecomputeMenuLayout()
  95. {
  96. SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
  97. SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
  98. }
  99. //////////////////
  100. // Make frame recalculate control bar sizes after menu change
  101. //
  102. void CMenuBar::RecomputeToolbarSize()
  103. {
  104. // Force toolbar to recompute size
  105. CFrameWnd* pFrame = (CFrameWnd*)GetOwner();
  106. ASSERT_VALID(pFrame);
  107. ASSERT(pFrame->IsFrameWnd());
  108. pFrame->RecalcLayout();
  109. // floating frame
  110. pFrame = GetParentFrame();
  111. if (pFrame->IsKindOf(RUNTIME_CLASS(CMiniFrameWnd)))
  112. pFrame->RecalcLayout();
  113. }
  114. //////////////////
  115. // Set tracking state: none, button, or popup
  116. //
  117. void CMenuBar::SetTrackingState(TRACKINGSTATE iState, int iButton)
  118. {
  119. ASSERT_VALID(this);
  120. if (iState != m_iTrackingState) {
  121. if (iState == TRACK_NONE)
  122. iButton = -1;
  123. #ifdef _DEBUG
  124. static LPCTSTR StateName[] = { _T("NONE"), _T("BUTTON"), _T("POPUP") };
  125. MBTRACE(_T("CMenuBar::SetTrackingState to %s, button=%dn"),
  126. StateName[iState], iButton);
  127. #endif
  128. SetHotItem(iButton);  // could be none (-1)
  129. if (iState==TRACK_POPUP) {
  130. // set related state stuff
  131. m_bEscapeWasPressed = FALSE;  // assume Esc key not pressed
  132. m_bProcessRightArrow =  // assume left/right arrow..
  133. m_bProcessLeftArrow = TRUE; // ..will move to prev/next popup
  134. m_iPopupTracking = iButton;  // which popup I'm tracking
  135. }
  136. m_iTrackingState = iState;
  137. }
  138. }
  139. //////////////////
  140. // Toggle state from home state to button-tracking and back
  141. //
  142. void CMenuBar::ToggleTrackButtonMode()
  143. {
  144. ASSERT_VALID(this);
  145. if (m_iTrackingState == TRACK_NONE || m_iTrackingState == TRACK_BUTTON) {
  146. SetTrackingState(m_iTrackingState == TRACK_NONE ?
  147. TRACK_BUTTON : TRACK_NONE, 0);
  148. }
  149. }
  150. //////////////////
  151. // Get button index before/after a given button
  152. //
  153. int CMenuBar::GetNextOrPrevButton(int iButton, BOOL bPrev)
  154. {
  155. ASSERT_VALID(this);
  156. if (bPrev) {
  157. iButton--;
  158. if (iButton <0)
  159. iButton = GetButtonCount() - 1;
  160. } else {
  161. iButton++;
  162. if (iButton >= GetButtonCount())
  163. iButton = 0;
  164. }
  165. return iButton;
  166. }
  167. /////////////////
  168. // This is to correct a bug in the system toolbar control: TB_HITTEST only
  169. // looks at the buttons, not the size of the window. So it returns a button
  170. // hit even if that button is totally outside the size of the window!
  171. //
  172. int CMenuBar::HitTest(CPoint p) const
  173. {
  174. int iHit = CFlatToolBar::HitTest(p);
  175. if (iHit>0) {
  176. CRect rc;
  177. GetClientRect(&rc);
  178. if (!rc.PtInRect(p)) // if point is outside window
  179. iHit = -1; // can't be a hit!
  180. }
  181. return iHit;
  182. }
  183. //////////////////
  184. // Load a different menu. The HMENU must not belong to any CMenu,
  185. // and you must free it when you're done. Returns old menu.
  186. //
  187. HMENU CMenuBar::LoadMenu(HMENU hmenu)
  188. {
  189. UINT iPrevID=(UINT)-1;
  190. ASSERT(::IsMenu(hmenu));
  191. ASSERT_VALID(this);
  192. if (m_bAutoRemoveFrameMenu) {
  193. CFrameWnd* pFrame = GetParentFrame();
  194. if (::GetMenu(*pFrame)!=NULL) {
  195. // I would like to set the frame's menu to NULL now, but if I do, MFC
  196. // gets all upset: it calls GetMenu and expects to have a real menu.
  197. // So Instead, I post a message to myself. Because the message is
  198. // posted, not sent, I won't process it until MFC is done with all its
  199. // initialization stuff. (MFC needs to set CFrameWnd::m_hMenuDefault
  200. // to the menu, which it gets by calling GetMenu.)
  201. //
  202. PostMessage(MB_SET_MENU_NULL, (WPARAM)pFrame->GetSafeHwnd());
  203. }
  204. }
  205. HMENU hOldMenu = m_hmenu;
  206. m_hmenu = hmenu;
  207. // delete existing buttons
  208. int nCount = GetButtonCount();
  209. while (nCount--) {
  210. VERIFY(DeleteButton(0));
  211. }
  212. SetImageList(NULL);
  213. // SetButtonSize(CSize(0,0)); // This barfs in VC 6.0
  214. DWORD dwStyle = GetStyle();
  215. BOOL bModifyStyle = ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT);
  216. // add text buttons
  217. UINT nMenuItems = hmenu ? ::GetMenuItemCount(hmenu) : 0;
  218. for (UINT i=0; i < nMenuItems; i++) {
  219. TCHAR name[64];
  220. memset(name, 0, sizeof(name)); // guarantees double-0 at end
  221. if (::GetMenuString(hmenu, i, name, countof(name)-1, MF_BYPOSITION)) {
  222. TBBUTTON tbb;
  223. memset(&tbb, 0, sizeof(tbb));
  224. tbb.idCommand = ::GetMenuItemID(hmenu, i);
  225. // Because the toolbar is too brain-damaged to know if it already has
  226. // a string, and is also too brain-dead to even let you delete strings,
  227. // I have to determine if each string has been added already. Otherwise
  228. // in a MDI app, as the menus are repeatedly switched between doc and
  229. // no-doc menus, I will keep adding strings until somebody runs out of
  230. // memory. Sheesh!
  231. // 
  232. int iString = -1;
  233. for (int j=0; j<m_arStrings.GetSize(); j++) {
  234. if (m_arStrings[j] == name) {
  235. iString = j; // found it
  236. break;
  237. }
  238. }
  239. if (iString <0) {
  240. // string not found: add it
  241. iString = AddStrings(name);
  242. m_arStrings.SetAtGrow(iString, name);
  243. }
  244. tbb.iString = iString;
  245. tbb.fsState = TBSTATE_ENABLED;
  246. tbb.fsStyle = TBSTYLE_AUTOSIZE;
  247. tbb.iBitmap = -1;
  248. tbb.idCommand = i;
  249. VERIFY(AddButtons(1, &tbb));
  250. }
  251. }
  252. if (bModifyStyle)
  253. SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
  254. if (hmenu) {
  255. AutoSize();  // size buttons
  256. RecomputeToolbarSize();  // and menubar itself
  257. }
  258. return hOldMenu;
  259. }
  260. //////////////////
  261. // Load menu from resource
  262. //
  263. HMENU CMenuBar::LoadMenu(LPCTSTR lpszMenuName)
  264. {
  265. return LoadMenu(::LoadMenu(AfxFindResourceHandle(lpszMenuName,RT_MENU),lpszMenuName));
  266. }
  267. //////////////////
  268. // Set the frame's menu to NULL. WPARAM is HWND of frame.
  269. //
  270. LRESULT CMenuBar::OnSetMenuNull(WPARAM wp, LPARAM lp)
  271. {
  272. HWND hwnd = (HWND)wp;
  273. ASSERT(::IsWindow(hwnd));
  274. ::SetMenu(hwnd, NULL);
  275. return 0;
  276. }
  277. //////////////////
  278. // Handle mouse click: if clicked on button, press it
  279. // and go into main menu loop.
  280. //
  281. void CMenuBar::OnLButtonDown(UINT nFlags, CPoint pt)
  282. {
  283. ASSERT_VALID(this);
  284. int iButton = HitTest(pt);
  285. if (iButton >= 0 && iButton<GetButtonCount()) // if mouse is over a button:
  286. TrackPopup(iButton);  //   track it
  287. else  // otherwise:
  288. CFlatToolBar::OnLButtonDown(nFlags, pt);  //   pass it on...
  289. }
  290. //////////////////
  291. // Handle mouse movement
  292. //
  293. void CMenuBar::OnMouseMove(UINT nFlags, CPoint pt)
  294. {
  295. ASSERT_VALID(this);
  296. if (m_iTrackingState==TRACK_BUTTON) {
  297. // In button-tracking state, ignore mouse-over to non-button area.
  298. // Normally, the toolbar would de-select the hot item in this case.
  299. // 
  300. // Only change the hot item if the mouse has actually moved.
  301. // This is necessary to avoid a bug where the user moves to a different
  302. // button from the one the mouse is over, and presses arrow-down to get
  303. // the menu, then Esc to cancel it. Without this code, the button will
  304. // jump to wherever the mouse is--not right.
  305. int iHot = HitTest(pt);
  306. if (IsValidButton(iHot) && pt != m_ptMouse)
  307. SetHotItem(iHot);
  308. return;  // don't let toolbar get it
  309. }
  310. m_ptMouse = pt; // remember point
  311. CFlatToolBar::OnMouseMove(nFlags, pt);
  312. }
  313. //////////////////
  314. // Window was resized: need to recompute layout
  315. //
  316. void CMenuBar::OnSize(UINT nType, int cx, int cy)
  317. {
  318. CFlatToolBar::OnSize(nType, cx, cy);
  319. RecomputeMenuLayout();
  320. }
  321. //////////////////
  322. // Bar style changed: eg, moved from left to right dock or floating
  323. //
  324. void CMenuBar::OnBarStyleChange(DWORD dwOldStyle, DWORD dwNewStyle)
  325. {
  326. CFlatToolBar::OnBarStyleChange(dwOldStyle, dwNewStyle);
  327. RecomputeMenuLayout();
  328. }
  329. /////////////////
  330. // When user selects a new menu item, note whether it has a submenu
  331. // and/or parent menu, so I know whether right/left arrow should
  332. // move to the next popup.
  333. //
  334. void CMenuBar::OnMenuSelect(HMENU hmenu, UINT iItem)
  335. {
  336. if (m_iTrackingState > 0) {
  337. // process right-arrow iff item is NOT a submenu
  338. m_bProcessRightArrow = (::GetSubMenu(hmenu, iItem) == NULL);
  339. // process left-arrow iff curent menu is one I'm tracking
  340. m_bProcessLeftArrow = hmenu==m_hMenuTracking;
  341. }
  342. }
  343. // globals--yuk! But no other way using windows hooks.
  344. //
  345. static CMenuBar* g_pMenuBar = NULL;
  346. static HHOOK g_hMsgHook = NULL;
  347. ////////////////
  348. // Menu filter hook just passes to virtual CMenuBar function
  349. //
  350. LRESULT CALLBACK
  351. CMenuBar::MenuInputFilter(int code, WPARAM wp, LPARAM lp)
  352. {
  353. return (code==MSGF_MENU && g_pMenuBar &&
  354. g_pMenuBar->OnMenuInput(*((MSG*)lp))) ? TRUE
  355. : CallNextHookEx(g_hMsgHook, code, wp, lp);
  356. }
  357. //////////////////
  358. // Handle menu input event: Look for left/right to change popup menu,
  359. // mouse movement over over a different menu button for "hot" popup effect.
  360. // Returns TRUE if message handled (to eat it).
  361. //
  362. BOOL CMenuBar::OnMenuInput(MSG& m)
  363. {
  364. ASSERT_VALID(this);
  365. ASSERT(m_iTrackingState == TRACK_POPUP); // sanity check
  366. int msg = m.message;
  367. if (msg==WM_KEYDOWN) {
  368. // handle left/right-arow.
  369. TCHAR vkey = m.wParam;
  370. if (vkey == VK_LEFT)
  371. MBTRACE(_T("CMenuBar::OnMenuInput: handle VK_LEFT - m_bProcessLeftArrow=%dn"),m_bProcessLeftArrow);
  372. else if (vkey == VK_RIGHT)
  373. MBTRACE(_T("CMenuBar::OnMenuInput: handle RIGHT - m_bProcessRightArrow=%dn"),m_bProcessRightArrow);
  374. if ((vkey == VK_LEFT && m_bProcessLeftArrow) ||
  375. (vkey == VK_RIGHT && m_bProcessRightArrow)) {
  376. CancelMenuAndTrackNewOne(
  377. GetNextOrPrevButton(m_iPopupTracking, vkey==VK_LEFT));
  378. return TRUE; // eat it
  379. } else if (vkey == VK_ESCAPE) {
  380. m_bEscapeWasPressed = TRUE;  // (menu will abort itself)
  381. }
  382. } else if (msg==WM_MOUSEMOVE || msg==WM_LBUTTONDOWN) {
  383. // handle mouse move or click
  384. CPoint pt = m.lParam;
  385. ScreenToClient(&pt);
  386. if (msg == WM_MOUSEMOVE) {
  387. if (pt != m_ptMouse) {
  388. int iButton = HitTest(pt);
  389. if (IsValidButton(iButton) && iButton != m_iPopupTracking) {
  390. // user moved mouse over a different button: track its popup
  391. CancelMenuAndTrackNewOne(iButton);
  392. }
  393. m_ptMouse = pt;
  394. }
  395. } else if (msg == WM_LBUTTONDOWN) {
  396. if (HitTest(pt) == m_iPopupTracking) {
  397. // user clicked on same button I am tracking: cancel menu
  398. MBTRACE(_T("CMenuBar:OnMenuInput: handle mouse click to exit popupn"));
  399. CancelMenuAndTrackNewOne(-1);
  400. return TRUE; // eat it
  401. }
  402. }
  403. }
  404. return FALSE; // not handled
  405. }
  406. //////////////////
  407. // Cancel the current popup menu by posting WM_CANCELMODE, and track a new
  408. // menu. iNewPopup is which new popup to track (-1 to quit).
  409. //
  410. void CMenuBar::CancelMenuAndTrackNewOne(int iNewPopup)
  411. {
  412. MBTRACE(_T("CMenuBar::CancelMenuAndTrackNewOne: %dn"), iNewPopup);
  413. ASSERT_VALID(this);
  414. if (iNewPopup != m_iPopupTracking) {
  415. GetOwner()->PostMessage(WM_CANCELMODE); // quit menu loop
  416. m_iNewPopup = iNewPopup;  // go to this popup (-1 = quit)
  417. }
  418. }
  419. //////////////////
  420. // Track the popup submenu associated with the i'th button in the menu bar.
  421. // This fn actually goes into a loop, tracking different menus until the user
  422. // selects a command or exits the menu.
  423. //
  424. void CMenuBar::TrackPopup(int iButton)
  425. {
  426. MBTRACE(_T("CMenuBar::TrackPopup %dn"), iButton);
  427. ASSERT_VALID(this);
  428. ASSERT(m_hmenu);
  429. CMenu menu;
  430. menu.Attach(m_hmenu);
  431. int nMenuItems = menu.GetMenuItemCount();
  432. while (iButton >= 0) {  // while user selects another menu
  433. m_iNewPopup = -1;  // assume quit after this
  434. PressButton(iButton, TRUE);  // press the button
  435. UpdateWindow();  // and force repaint now
  436. // post a simulated arrow-down into the message stream
  437. // so TrackPopupMenu will read it and move to the first item
  438. GetOwner()->PostMessage(WM_KEYDOWN, VK_DOWN, 1);
  439. GetOwner()->PostMessage(WM_KEYUP, VK_DOWN, 1);
  440. SetTrackingState(TRACK_POPUP, iButton); // enter tracking state
  441. // Need to install a hook to trap menu input in order to make
  442. // left/right-arrow keys and "hot" mouse tracking work.
  443. //
  444. ASSERT(g_pMenuBar == NULL);
  445. g_pMenuBar = this;
  446. ASSERT(g_hMsgHook == NULL);
  447. g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,
  448. MenuInputFilter, NULL, ::GetCurrentThreadId());
  449. // get submenu and display it beneath button
  450. TPMPARAMS tpm;
  451. CRect rcButton;
  452. GetRect(iButton, rcButton);
  453. ClientToScreen(&rcButton);
  454. CPoint pt = ComputeMenuTrackPoint(rcButton, tpm);
  455. HMENU hMenuPopup = ::GetSubMenu(m_hmenu, iButton);
  456. ASSERT(hMenuPopup);
  457. m_hMenuTracking = hMenuPopup;
  458. BOOL bRet = TrackPopupMenuEx(hMenuPopup,
  459. TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_VERTICAL,
  460. pt.x, pt.y, GetOwner()->GetSafeHwnd(), &tpm);
  461. // uninstall hook.
  462. ::UnhookWindowsHookEx(g_hMsgHook);
  463. g_hMsgHook = NULL;
  464. g_pMenuBar = NULL;
  465. PressButton(iButton, FALSE);  // un-press button
  466. UpdateWindow();  // and force repaint now
  467. // If the user exited the menu loop by pressing Escape,
  468. // return to track-button state; otherwise normal non-tracking state.
  469. SetTrackingState(m_bEscapeWasPressed ?
  470. TRACK_BUTTON : TRACK_NONE, iButton);
  471. // If the user moved mouse to a new top-level popup (eg from File to
  472. // Edit button), I will have posted a WM_CANCELMODE to quit
  473. // the first popup, and set m_iNewPopup to the new menu to show.
  474. // Otherwise, m_iNewPopup will be -1 as set above.
  475. // So just set iButton to the next popup menu and keep looping...
  476. iButton = m_iNewPopup;
  477. }
  478. menu.Detach();
  479. m_hMenuTracking = m_hmenu;
  480. }
  481. //////////////////
  482. // Given button rectangle, compute point and "exclude rect" for
  483. // TrackPopupMenu, based on current docking style, so that the menu will
  484. // appear always inside the window.
  485. //
  486. CPoint CMenuBar::ComputeMenuTrackPoint(const CRect& rcButn, TPMPARAMS& tpm)
  487. {
  488. tpm.cbSize = sizeof(tpm);
  489. DWORD dwStyle = m_dwStyle;
  490. CPoint pt;
  491. CRect& rcExclude = (CRect&)tpm.rcExclude;
  492. rcExclude = rcButn;
  493. ::GetWindowRect(::GetDesktopWindow(), &rcExclude);
  494. switch (dwStyle & CBRS_ALIGN_ANY) {
  495. case CBRS_ALIGN_BOTTOM:
  496. pt = CPoint(rcButn.left, rcButn.top);
  497. rcExclude.top = rcButn.top;
  498. break;
  499. case CBRS_ALIGN_LEFT:
  500. pt = CPoint(rcButn.right, rcButn.top);
  501. rcExclude.right = rcButn.right;
  502. break;
  503. case CBRS_ALIGN_RIGHT:
  504. pt = CPoint(rcButn.left, rcButn.top);
  505. rcExclude.left = rcButn.left;
  506. break;
  507. default: // case CBRS_ALIGN_TOP:
  508. pt = CPoint(rcButn.left, rcButn.bottom);
  509. break;
  510. }
  511. return pt;
  512. }
  513. //////////////////
  514. // This function translates special menu keys and mouse actions.
  515. // You must call it from your frame's PreTranslateMessage.
  516. //
  517. BOOL CMenuBar::TranslateFrameMessage(MSG* pMsg)
  518. {
  519. ASSERT_VALID(this);
  520. ASSERT(pMsg);
  521. UINT msg = pMsg->message;
  522. if (WM_LBUTTONDOWN <= msg && msg <= WM_MOUSELAST) {
  523. if (pMsg->hwnd != m_hWnd && m_iTrackingState > 0) {
  524. // user clicked outside menu bar: exit tracking mode
  525. MBTRACE(_T("CMenuBar::TranslateFrameMessage: user clicked outside menu bar: end trackingn"));
  526. SetTrackingState(TRACK_NONE);
  527. }
  528. } else if (msg==WM_SYSKEYDOWN || msg==WM_SYSKEYUP || msg==WM_KEYDOWN) {
  529. BOOL bAlt = HIWORD(pMsg->lParam) & KF_ALTDOWN; // Alt key down
  530. TCHAR vkey = pMsg->wParam;   // get virt key
  531. if (vkey==VK_MENU ||
  532. (vkey==VK_F10 && !((GetKeyState(VK_SHIFT) & 0x80000000) ||
  533.                    (GetKeyState(VK_CONTROL) & 0x80000000) || bAlt))) {
  534. // key is VK_MENU or F10 with no alt/ctrl/shift: toggle menu mode
  535. if (msg==WM_SYSKEYUP) {
  536. MBTRACE(_T("CMenuBar::TranslateFrameMessage: handle menu keyn"));
  537. ToggleTrackButtonMode();
  538. }
  539. return TRUE;
  540. } else if ((msg==WM_SYSKEYDOWN || msg==WM_KEYDOWN)) {
  541. if (m_iTrackingState == TRACK_BUTTON) {
  542. // I am tracking: handle left/right/up/down/space/Esc
  543. switch (vkey) {
  544. case VK_LEFT:
  545. case VK_RIGHT:
  546. // left or right-arrow: change hot button if tracking buttons
  547. MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_LEFT/RIGHTn"));
  548. SetHotItem(GetNextOrPrevButton(GetHotItem(), vkey==VK_LEFT));
  549. return TRUE;
  550. case VK_SPACE:  // (personally, I like SPACE to enter menu too)
  551. case VK_UP:
  552. case VK_DOWN:
  553. // up or down-arrow: move into current menu, if any
  554. MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_UP/DOWN/SPACEn"));
  555. TrackPopup(GetHotItem());
  556. return TRUE;
  557. case VK_ESCAPE:
  558. // escape key: exit tracking mode
  559. MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_ESCAPEn"));
  560. SetTrackingState(TRACK_NONE);
  561. return TRUE;
  562. }
  563. }
  564. // Handle alphanumeric key: invoke menu. Note that Alt-X
  565. // chars come through as WM_SYSKEYDOWN, plain X as WM_KEYDOWN.
  566. if ((bAlt || m_iTrackingState == TRACK_BUTTON) && isalnum(vkey)) {
  567. // Alt-X, or else X while in tracking mode
  568. UINT nID;
  569. if (MapAccelerator(vkey, nID)) {
  570. MBTRACE(_T("CMenuBar::TranslateFrameMessage: map accleratorn"));
  571. TrackPopup(nID);  // found menu mnemonic: track it
  572. return TRUE;  // handled
  573. } else if (m_iTrackingState==TRACK_BUTTON && !bAlt) {
  574. MessageBeep(0);
  575. return TRUE;
  576. }
  577. }
  578. // Default for any key not handled so far: return to no-menu state
  579. if (m_iTrackingState > 0) {
  580. MBTRACE(_T("CMenuBar::TranslateFrameMessage: unknown key, stop trackingn"));
  581. SetTrackingState(TRACK_NONE);
  582. }
  583. }
  584. }
  585. return FALSE; // not handled, pass along
  586. }
  587. #ifdef _DEBUG
  588. void CMenuBar::AssertValid() const
  589. {
  590. CFlatToolBar::AssertValid();
  591. ASSERT(m_hmenu==NULL || ::IsMenu(m_hmenu));
  592. ASSERT(TRACK_NONE<=m_iTrackingState && m_iTrackingState<=TRACK_POPUP);
  593. m_frameHook.AssertValid();
  594. }
  595. void CMenuBar::Dump(CDumpContext& dc) const
  596. {
  597. CFlatToolBar::Dump(dc);
  598. }
  599. #endif
  600. //////////////////////////////////////////////////////////////////
  601. // CMenuBarFrameHook is used to trap menu-related messages sent to the owning
  602. // frame. The same class is also used to trap messages sent to the MDI client
  603. // window in an MDI app. I should really use two classes for this,
  604. // but it uses less code to chare the same class. Note however: there
  605. // are two different INSTANCES of CMenuBarFrameHook in CMenuBar: one for
  606. // the frame and one for the MDI client window.
  607. //
  608. CMenuBarFrameHook::CMenuBarFrameHook()
  609. {
  610. }
  611. CMenuBarFrameHook::~CMenuBarFrameHook()
  612. {
  613. HookWindow((HWND)NULL); // (unhook)
  614. }
  615. //////////////////
  616. // Install hook to trap window messages sent to frame or MDI client.
  617. // 
  618. BOOL CMenuBarFrameHook::Install(CMenuBar* pMenuBar, HWND hWndToHook)
  619. {
  620. ASSERT_VALID(pMenuBar);
  621. m_pMenuBar = pMenuBar;
  622. return HookWindow(hWndToHook);
  623. }
  624. //////////////////////////////////////////////////////////////////
  625. // Trap frame/MDI client messages specific to menubar. 
  626. //
  627. LRESULT CMenuBarFrameHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
  628. {
  629. CMenuBar& mb = *m_pMenuBar;
  630. switch (msg) {
  631. // The following messages are trapped for the frame window
  632. case WM_SYSCOLORCHANGE:
  633. mb.UpdateFont();
  634. break;
  635. case WM_MENUSELECT:
  636. mb.OnMenuSelect((HMENU)lp, (UINT)LOWORD(wp));
  637. break;
  638. }
  639. return CSubclassWnd::WindowProc(msg, wp, lp);
  640. }