# 菜单简述
Windows 的菜单分为两类:
窗口菜单是挂接到窗口上的;
弹出式菜单是根据程序设计者的需要在某处弹出的。
# 添加菜单资源
- 右键
项目
, 添加
-> 资源
- 选择
Menu
,点击 新建
,进入资源编辑界面。
- 顶层菜单 (类型为窗口菜单) 添加一个项
新建
、顶层的子菜单 (类型为弹出式菜单) 添加一个项 文件
- 修改顶层菜单的资源为
IDR_MENU_TOP
- 修改顶层菜单的
新建
项的子菜单的 文件
项的资源为 ID_MENU_FILE_NEW
。
注:此处附注的菜单类型并不需要你在编辑时设置,是指明该菜单属于什么类型。
顶层菜单的类型就是 窗口菜单
顶层菜单的子菜单的类型就是 弹出式菜单
资源编辑界面示范:
# 使用窗口菜单
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <afxwin.h>
#include "resource.h"
class CMyFrameWnd :public CFrameWnd { DECLARE_MESSAGE_MAP() public: afx_msg int OnCreate(LPCREATESTRUCT pCs); afx_msg void OnClose(); afx_msg void OnMenu_File_New(); private: CMenu* m_pMenu; };
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd) ON_WM_CREATE() ON_WM_CLOSE() ON_COMMAND(ID_MENU_FILE_NEW, OnMenu_File_New) END_MESSAGE_MAP()
afx_msg int CMyFrameWnd::OnCreate(LPCREATESTRUCT pCs) { m_pMenu = new CMenu; m_pMenu->LoadMenu(IDR_MENU_TOP); SetMenu(m_pMenu); return CFrameWnd::OnCreate(pCs); }
afx_msg void CMyFrameWnd::OnClose() { if (m_pMenu) { delete m_pMenu; m_pMenu = nullptr; } CFrameWnd::OnClose(); }
afx_msg void CMyFrameWnd::OnMenu_File_New() { AfxMessageBox(L"new"); }
class CMyWinApp :public CWinApp { public: CMyWinApp() {
} virtual BOOL InitInstance() { CMyFrameWnd* frame = new CMyFrameWnd; frame->Create(nullptr, L"MFCBase"); m_pMainWnd = frame; frame->ShowWindow(SW_SHOW); frame->UpdateWindow(); return TRUE; } };
CMyWinApp g_theApp;
|
代码想必读者也比较熟悉了,有一些需要注意的改动如下:
CMyFrameWnd
添加私有成员 CMenu* m_pMenu
;
- 添加了一个新的消息映射条目
ON_COMMAND(ID_MENU_FILE_NEW, OnMenu_File_New)
;
- 为消息映射函数添加了宏
afx_msg
;
# 挂接窗口菜单
首先,代码中 new
了一个 CMenu
类对象,使我们添加的私有成员指向它,这是 MFC 封装的菜单类。
其次,调用了 CMenu::LoadMenu
,用于加载我们添加的菜单资源。
最后,以当前 CMyFrameWnd
类对象的身份,调用了 CFrameWnd::SetMenu
成员函数。
至此,我们完成了将窗口菜单挂接到 CMyFrameWnd
类对象上的工作。
# 处理菜单项回调事件
ON_COMMAND(ID_MENU_FILE_NEW, OnMenu_File_New)
CMyFrameWnd::OnMenu_File_New
只需要通过 ON_COMMAND
宏,就可以建立指定菜单资源被点击 ( WM_COMMAND
消息) 与回调函数的映射。
# WM_COMMAND
命令消息,在菜单被点击等事件发生时产生。
在 MFC 的消息处理架构中, WM_COMMAND
的处理流程与我们先前接触的 WM_CREATE
等消息是不同的。
我们回到 CWnd::OnWndMsg
函数,在函数开始部分就能看到相关源码。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { LRESULT lResult = 0; union MessageMapFunctions mmf; mmf.pfn = 0; CInternalGlobalLock winMsgLock; if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } ...
|
只要是 WM_COMMAND
消息,会调用 CWnd::OnCommand
,返回后就再次返回了。
最终会走到全局静态函数 _AfxDispatchCmdMsg
中回调 CMyFrameWnd::OnMenu_File_New
。
由于代码较为繁琐,故不再向下深入,读者感兴趣可以自行调试。
# 使用弹出式菜单
DefWindowProc
在处理 WM_RBUTTONUP
或 WM_NCRBUTTONUP
消息,或是用户键入 SHIFT+F10
时,生成 WM_CONTEXTMENU
消息。
# 实现右键客户区弹出菜单
我们知道,在菜单编辑界面中,顶层菜单的子菜单都是独立的弹出式菜单;
- 故这些子菜单都是可以被单独弹出的
- 而顶层菜单项被点击时弹出子菜单不需要我们处理,是因为 windows 替我们做好了相关的工作。
接下来我们通过映射 WM_CONTEXTMENU
消息,实现在客户区右键,弹出顶层菜单 文件
的子菜单。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <afxwin.h>
#include "resource.h"
class CMyFrameWnd :public CFrameWnd { DECLARE_MESSAGE_MAP() public: afx_msg int OnCreate(LPCREATESTRUCT pCs); afx_msg void OnClose(); afx_msg void OnMenu_File_New(); afx_msg void OnContextMenu(CWnd* wnd, CPoint pos); private: CMenu* m_pMenu; };
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd) ON_WM_CREATE() ON_WM_CLOSE()
ON_COMMAND(ID_MENU_FILE_NEW, OnMenu_File_New) ON_WM_CONTEXTMENU() END_MESSAGE_MAP()
afx_msg int CMyFrameWnd::OnCreate(LPCREATESTRUCT pCs) { m_pMenu = new CMenu; m_pMenu->LoadMenu(IDR_MENU_TOP); SetMenu(m_pMenu); return CFrameWnd::OnCreate(pCs); }
afx_msg void CMyFrameWnd::OnClose() { if (m_pMenu) { delete m_pMenu; m_pMenu = nullptr; } CFrameWnd::OnClose(); }
void CMyFrameWnd::OnContextMenu(CWnd* wnd, CPoint pos) { CMenu* pNewMenu = m_pMenu->GetSubMenu(0); pNewMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN, pos.x, pos.y, wnd); }
afx_msg void CMyFrameWnd::OnMenu_File_New() { AfxMessageBox(L"new");
}
class CMyWinApp :public CWinApp { public: CMyWinApp() {
} virtual BOOL InitInstance() { CMyFrameWnd* frame = new CMyFrameWnd; frame->Create(nullptr, L"MFCBase"); m_pMainWnd = frame; frame->ShowWindow(SW_SHOW); frame->UpdateWindow(); return TRUE; } };
CMyWinApp g_theApp;
|
代码并不复杂,建议读者自行尝试调试运行。
参考资料:
https://www.cnblogs.com/hanford/p/6163690.html
https://www.cnblogs.com/greenleaf1976/p/16460330.html