📄 menubar.cpp
字号:
//////////////////
// Bar style changed: eg, moved from left to right dock or floating
//
void CMenuBar::OnBarStyleChange(DWORD dwOldStyle, DWORD dwNewStyle)
{
CCJToolBar::OnBarStyleChange(dwOldStyle, dwNewStyle);
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 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.
::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();
}
//////////////////
// Given button rectangle, compute point and "exclude rect" for
// TrackPopupMenu, based on current docking style, so that the menu will
// appear always inside the window.
//
CPoint CMenuBar::ComputeMenuTrackPoint(const CRect& rcButn, TPMPARAMS& tpm)
{
tpm.cbSize = sizeof(tpm);
DWORD dwStyle = m_dwStyle;
CPoint pt;
CRect& rcExclude = (CRect&)tpm.rcExclude;
rcExclude = rcButn;
::GetWindowRect(::GetDesktopWindow(), &rcExclude);
switch (dwStyle & CBRS_ALIGN_ANY) {
case CBRS_ALIGN_BOTTOM:
pt = CPoint(rcButn.left, rcButn.top);
rcExclude.top = rcButn.top;
break;
case CBRS_ALIGN_LEFT:
pt = CPoint(rcButn.right, rcButn.top);
rcExclude.right = rcButn.right;
break;
case CBRS_ALIGN_RIGHT:
pt = CPoint(rcButn.left, rcButn.top);
rcExclude.left = rcButn.left;
break;
default: // case CBRS_ALIGN_TOP:
pt = CPoint(rcButn.left, rcButn.bottom);
break;
}
return pt;
}
//////////////////
// 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
}
#ifdef _DEBUG
void CMenuBar::AssertValid() const
{
CCJToolBar::AssertValid();
ASSERT(m_hmenu==NULL || ::IsMenu(m_hmenu));
ASSERT(TRACK_NONE<=m_iTrackingState && m_iTrackingState<=TRACK_POPUP);
m_frameHook.AssertValid();
}
void CMenuBar::Dump(CDumpContext& dc) const
{
CCJToolBar::Dump(dc);
}
#endif
//////////////////////////////////////////////////////////////////
// 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 chare 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) {
// The following messages are trapped for the frame window
case WM_SYSCOLORCHANGE:
mb.UpdateFont();
break;
case WM_MENUSELECT:
mb.OnMenuSelect((HMENU)lp, (UINT)LOWORD(wp));
break;
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -