# 菜单简述

Windows 的菜单分为两类:

  • 窗口菜单
  • 弹出式菜单

窗口菜单是挂接到窗口上的;
弹出式菜单是根据程序设计者的需要在某处弹出的。

# 添加菜单资源

  1. 右键 项目添加 -> 资源
  2. 选择 Menu ,点击 新建 ,进入资源编辑界面。
  3. 顶层菜单 (类型为窗口菜单) 添加一个项 新建 、顶层的子菜单 (类型为弹出式菜单) 添加一个项 文件
  4. 修改顶层菜单的资源为 IDR_MENU_TOP
  5. 修改顶层菜单的 新建 项的子菜单的 文件 项的资源为 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;

代码想必读者也比较熟悉了,有一些需要注意的改动如下:

  1. CMyFrameWnd 添加私有成员 CMenu* m_pMenu
  2. 添加了一个新的消息映射条目 ON_COMMAND(ID_MENU_FILE_NEW, OnMenu_File_New)
  3. 为消息映射函数添加了宏 afx_msg
    • 并无特殊作用,仅为提高代码可读性。

# 挂接窗口菜单

CMyFrameWnd::OnCreate

首先,代码中 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;
// special case for commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
...

只要是 WM_COMMAND 消息,会调用 CWnd::OnCommand ,返回后就再次返回了。

最终会走到全局静态函数 _AfxDispatchCmdMsg 中回调 CMyFrameWnd::OnMenu_File_New

由于代码较为繁琐,故不再向下深入,读者感兴趣可以自行调试。

# 使用弹出式菜单

# WM_CONTEXTMENU

DefWindowProc 在处理 WM_RBUTTONUPWM_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