菜单项和菜单命令响应函数
1、创建基于单文档工程Menu,添加菜单项:IDM_TEST Test
2、通过类向导, 为IDM_TEST在CMainFrame、CMenuView、CMenuDoc、CMenuApp下添加菜单命令响应函数,即WM_COMMAND响应函数。
通过实验发现:
1、响应Test菜单项命令的顺序依次是:视图、文档类、框架类、应用程序类。
2、菜单命令消息路由的具体过程:
①点击某菜单项,框架类最先接到菜单命令消息。
②框架类把接收到得这个消息交给它的子窗口,即视图类。
③视图类根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束。
④如果视图类未对此消息响应,交给文档类,文档类同样查找自身是否对此消息进行了响应。
⑤如果文档类为作出响应,再交还给视图类,视图类把它交还给框架类。
⑥框架类查看自身,如果未响应,就把该菜单消息命令交给应用程序类进行处理。
3、添加Test菜单项的命令响应函数后,在三处进行了添加:
eg:在视图类中添加Test菜单命令响应函数
①在视图类头文件中,两个AFX_MSG注释宏间,添加了命令消息响应函数原型
afx_msg void OnTest();
②在视图类的源文件中,两个AFX_MSG_MAP注释宏之间添加了ON_COMMAND宏,将菜单ID和命令响应函数关联起来。
ON_COMMAND(IDM_TEST,OnTest)
③视图类源文件中有命令消息响应函数实现代码。
相关知识注:
1、资源ID号命名:eg:
IDM:Menu/IDC:Cursor/IDI:Icon
2、C*App/C*Doc都不是从CWnd派生,没有MessageBox成员函数,可以使用全局的MessageBox函数,或者使用应用程序框架的函数:AfxMessageBox
int AfxMessageBox(LPCTSTR lpszText, UINT nType = MB_OK,
UINT nIDHelp = 0)
AfxMessgeBox函数后两个参数为默认值,只需给第一个参数赋值就行。
eg:AfxMessageBox(“Hello”);
菜单命令的路由
Windows消息分类:
①标准消息:除WM_COMMAND之外,所有以WM_开头的消息都是标准消息。
②命令消息:以WM_COMMAND形式,来自菜单、加速键、工具栏按钮的消息。
③通告消息:WM_COMMAND形式,由控件产生,目的是向父窗口(通常是对话框)通知事件的发生。
CWnd类派生于CCmdTarget类,凡是从CWnd派生的类即可接收标准消息,也可接收命令消息和通告消息。从CCmdTarget派生的类,只能接收命令消息和通告消息,不能接收标准消息。eg:该例中CMenuDoc和CWinApp都派生于CCmdTarget类,所以可以接收菜单命令消息,因为不是从CWnd类派生的,不能接收标准消息。
消息路由
命令消息使用的是ON_COMMAND宏,其路由过程同标准消息还是有区别的:
①OnWndMsg函数对消息类型判断,若是标准消息,利用一般消息映射机制,查找哪个类响应了当前消息。
②若是命令消息,交给OnCommand函数处理,在OnCommand函数中完成消息路由。
③若是通告消息,交给OnNotify函数处理。
④OnCommand和OnNotify最后都会调用OnCmdMsg函数。
基本菜单操作
1、认识菜单
如图,【文件】为第一个子菜单,索引为0,【编辑】为第二个子菜单,索引为1。这些默认生成的子菜单都没有ID。
【新建】为文件下第一个菜单项,索引为0,有自己ID:ID_FILE_NEW;
【打印】ID为ID_FILE_PRINT,索引为5,而不是4。原因是其上有一分隔栏,分隔栏在菜单项中占据索引位置。
可以通过ID和索引访问菜单项,只能通过索引访问默认子菜单。
2、菜单标记项
使用示例:为【文件】子菜单中【新建】菜单项添加标记功能。
在CMainFrame类得OnCreate函数中添加:
GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION |
MF_CHECKED);
注:
1、【文件】和【新建】索引号均为0
2、CMenu* GetMenu() const;获得指向菜单栏的指针。GetMenu函数是CWnd类得成员函数。
CMenu类提供许多菜单操作有关的成员函数,eg:
CMenu* GetSubMenu(int nPos) const;获得指向nPos指定子菜单的指针
3、为菜单项添加或移除标记的函数:
UINT CheckMenuItem(UINT nIDCheckItem, UINT nCheck);
nIDCheckItem指定需要处理的菜单项,取值由nCheck决定。
nCheck指定如何设置菜单项,如何定位菜单项位置,取值为:
MF_CHECKED:设置菜单项的复选标记
MF_UNCHECKED:移走复选标记
MF_BYPOSITION:根据菜单项位置访问菜单项,即第一个参数指定菜单项索引号
MF_BYCOMMAND:根据菜单项的命令访问菜单项,即第一个参数指定菜单项命令ID
3、默认菜单项
菜单项粗体显示,为默认菜单项
BOOL SetDefaultItem(UINT uItem, BOOL fByPos = FALSE);
uItem:ID或索引,由参数2决定
fByPos:FALSE表示参数1为ID,TRUE表为索引。
使用示例:将【打印】设为默认菜单项
GetMenu()->GetSubMenu(0)->SetDefaultItem(5,TRUE);
GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_PRINT,TRUE);
注:一个子菜单只能设定一个默认项
4、图形标记菜单
BOOL SetMenuItemBitmaps(UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked);
参数一:ID或索引,由参数二决定
参数二:MF_BYCOMMAND:参数一为ID
MF_BYPOSITION:索引
参数三:指定取消菜单项选中状态时位图
参数四:指定选中时的位图
使用示例:
在CMainFrame类中添加成员变量:CBitmap m_bitmap
导入或创建位图资源:ID为IDB_BITMAP1
在CMainFrame的Oncreate函数中添加:
m_bitmap.LoadBitmap(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,
&m_bitmap,&m_bitmap)
注:
1、应将位图大小设置为图形标记菜单上显示的位图的尺寸,否则只显示部分。
2、获得图形标记菜单上位图尺寸:
int GetSystemMetrics(int nIndex);
nIndex:指定希望获得那部分系统信息。
值为SM_CXMENUCHECK或SM_CYMENUCHECK时,函数获取标记菜单上标记图形默认尺寸,前者表宽度,后者为高度。
代码:
CString str;
str.Format(“x=%d,y=%d”,GetSystemMetrics(SM_CXMENUCHECK),
GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
这样就可获得位图大小信息。
5、禁用菜单项
UINT EnableMenuItem(UINT nIDEnableItem, UINT nEnable);
参数一:ID或索引,由参数二决定。
参数二:可以是以下参数的组合
MF_BYCOMMAND MF_BYPOSITION MF_DISABLED MF_ENABLED MF_GRAYED
其中MF_DIABLED只不可用并不变灰,常将MF_DISABLED和MF_GRAYED联合使用。
代码:CMainFrame的Oncreate函数中:
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSIONT
| MF_DISBLED | MF_GRAYED);
还要在CMainFrame的构造函数中将m_bAutoMenuEnable设为FALSE;
m_bAutoMenuEnable = FALSE;
注:
1、原因:如果想要改变菜单项状态,就必须把m_bAutoMenuEnable设为FALSE,之后对菜单项状态的更改才起作用。
2、将m_bAutoMenuEnable设为FALSE后,就不对ON_UPDATE_COMMAND_UI或ON_COMMAND消息进行响应处理,CMenu类的EnableMenuItem函数将能够正常工作。
此后,原菜单项的默认设置将不起作用,需要用户设置(eg:原有的灰色项将不再为灰色)。
6、移除和装载菜单
1、BOOL SetMenu(CMenu* pMenu);pMenu若为NULL,当前菜单被移除。
eg:CMainFrame的OnCreate函数中添加:
SetMenu(NULL);//程序中菜单被移除
2、重新装载
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);//IDR_MAINFRAME为菜单ID
SetMenu(&menu);
注:菜单资源同位图资源一样,也要加载到菜单对象中,然后调用SetMenu把菜单设置为刚刚加载的菜单对象。
注:此处的错误:menu是一局部变量,会引发错误。
1、方法一:将menu设为CMainFrame的成员变量
2、方法二:使用Detach将菜单句柄同菜单对象分离,就不会受menu生存期的影响。
即添加:menu.Detach();
MFC菜单命令更新机制
1、如果要在程序中设置某个菜单项的状态,通过类向导添加UPDATE_COMMAND_UI消息响应函数,然后在函数中进行相应设置。
2、添加了UPDATE_COMMAND_UI消息响应函数后
当要显示菜单时,操作系统发出WM_INITMENUPOPUP消息,然后由程序窗口的基类如CFrameWnd接管
它创建一个CCmdUI对象,与程序中第一个菜单项关联,调用该对象的一个成员函数DoUpdate()。
该函数发出CN_UPDADTE_COMMAND_UI消息,该消息带有一个指向CCmdUI对象的指针。
这时,系统判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕捉该消息。
如果找到就交给相应消息响应函数去处理。函数中利用传递过来的CCmdUI对象去调用相应函数。
更新完第一个菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依次进行直到完成所有菜单项的设置。
3、工具栏和菜单栏上对应项ID相同。工具栏中分隔符也占据索引。工具栏和菜单栏对应项索引值不同。
4、菜单项的维护依赖CN_UPDATE_COMMAND_UI消息。通过在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获。
4、使用示例:
通过类向导为ID_EDIT_CUT添加UPDATE_COMMAND_UI响应函数。
这样在CMainFrame类的消息映射中就添加了一个ON_UPDATE_COMMAND_UI宏。
ON_UPDATE_COMMAND_UI(ID_EDIT_CUT,OnUpdateEditCut)
在OnUpdateEditCut中添加:
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
pCmdUI->Enable();//使菜单项可用
//pCmdUI->Enable(FALSE);// 使菜单项禁用
//此时pCmdUI指向ID_EDIT_CUT菜单项
}
快捷菜单
TrackPopupMenu函数显示一快捷菜单。
BOOL TrackPopupMenu(UINT nFlags, int x, int y, CWnd* pWnd,
LPCRECT lpRect = NULL);
nFlags:菜单在屏幕上显示位置
x,y:显示位置处得x和y坐标
pWnd:快捷菜单拥有者
lpRect:指定一矩形区域。用户在区域内单击鼠标,快捷菜单也保持显示。设为NULL,则在快捷菜单范围外单击鼠标,菜单消失。默认值为NULL。
示例:
添加一菜单资源IDR_MENU1,添加相应菜单项。
在CMenuView的OnRButtonDown函数中添加:
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu* pPopup = menu.GetSubMenu(0);
ClientToScreen(&point);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
point.x, point.y, this);
析:
1、视窗覆盖在主框架之上,所以未在CMainFrame类中添加代码,而是在C*View类中。
2、TrackPopupMenu中坐标是屏幕坐标,而鼠标单击处坐标是窗口客户区坐标(以程序窗口左上角为坐标原点),因此应把客户区坐标转化为屏幕坐标。
使用ClientToScreen。
3、this指针表明快捷菜单为视窗类所有。若让其为主框架类所有,this改为GetParent()。但这样只是给主框架类获得消息处理的机会,如果视窗类和主框架类中都有快捷菜单菜单项单击事件响应函数,视窗类中的函数会响应。
动态菜单操作
包括:针对弹出菜单的动态操作和针对菜单项的动态操作
1、添加菜单项目:
BOOL AppendMenu(UINT nFlags, UINT_PTR nIDNewItem = 0,
LPCTSTR lpszNewItem = NULL);
nFlags:指定新添加的菜单项目的状态信息
nIDNewItem:参数一为MF_POPUP,nIDNewItem为顶层菜单句柄,否则为要添加的新菜单项ID。参数一为MF_SEPATATOR,nIDNewItem值被忽略。
lpszNewItem:参数一为MF_STRING,lpszNewItem为指向要添加的新菜单项目的文本指针。参数一为MF_OWNERDRAW,lpszNewItem为指向该菜单项目的一个附加数据的指针。参数一为MF_SEPARATOR,lpszNewItem值被忽略。
代码:
CMenu menu;
menu.CreateMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,”Test”);
menu.Detach();
析:
1、可用menu.CreatePopupMenu();创建一弹出菜单并与menu对象关联。
2、menu.Detach();将菜单句柄和对象分离。也可使用将menu设为CMainFrame的成员变量的方式。(解决局部变量造成的错误问题)
3、CMenu类的成员变量m_hMenu是菜单句柄,为HMENU类型,强制转换为UINT类型。
2、插入菜单项
BOOL InsertMenu(UINT nPosition, UINT nFlags, UINT_PTR nIDNewItem = 0, LPCTSTR lpszNewItem = NULL);
nFlags在AppendMenu的nFlags基础上添加:MF_BYCOMMAND(此时nPostion为一菜单命令标识,表明在nPostion指定菜单项后添加)或MF_BYPOSITION(此时nPosition为索引值,表新添加项的位置)。
示例:
CMenu menu;
menu.CreateMenu();
GetMenu()->InsertMenu(2,MF_POPUP | MF_POSITION,
(UINT)menu.m_hmenu,”Test”);
//在索引2位置添加菜单子菜单Test
menu.AppendMenu(MF_STRING,111,”Hello”);
menu.AppendMenu(MF_STRING,112,”Well”);
//在Test下添加菜单项
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,113,”Welcome”);
//在文件子菜单下添加菜单项Welcome
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,
MF_COMMAND | MF_STRING,114,”VC编程”);
//文件子菜单下,打开菜单项后添加菜单项
GetMenu()->GetSubMenu(0)->InsertMenu(4,
MF_POSITION | MF_STRING,115,”VC++”);
//文件子菜单下,索引4位置添加菜单项
menu.Detach();
注:111为指向要添加的新菜单项的文本的指针。选择数据有什么要求?此处Hello为灰色禁用状态,若换为0,则为可用,为什么?
3、删除菜单
BOOL DelteMenu(UINT nPosition, UINT nFlags);
可以删除子菜单及子菜单下一个菜单项。
如果调用函数的是菜单栏对象,删除指定子菜单。
如果是一子菜单对象,删除子菜单下菜单项。
GetMenu()->DeleteMenu(1,MF_BYPOSITION);
//删除了编辑子菜单
GetMenu()->GetSubMenu(0)->DeleteMenu(1,MF_BYPOSITION);
//删除了文件子菜单下的索引为1的菜单项
4、动态添加菜单项的命令响应
示例:Test子菜单下Hello菜单项,添加命令消息响应函数
为a菜单项创建一菜单资源ID
①在Resource.h文件中,手工添加:
#define IDM_HELLO 111
②原来的代码:
menu.AppendMenu(MF_STRING,111,” Hello”);
可以改为menu.AppendMenu(MF_STRING,IDM_A,” Hello”);
③为该菜单项添加命令消息响应函数
遵循MFC消息映射机制,需要在三处进行添加。
⑴在响应该菜单项命令的程序类(本例为CMainFrame)头文件中添加响应函数原型。
在声明消息映射宏(DECLARE_MESSAGE_MAP)之上,两个AFX_MSG注释宏后。(放在AFX_MSG注释宏后表示自己手动添加,放在之间是系统自动添加的)
afx_msg void OnHello();
⑵在响应这个菜单项命令的程序类的源文件中消息映射表中添加消息映射。
在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏之间,两个AFX_MSG_MAP注释宏之后。添加ON_COMMAND宏。(同样,放在AFX_MSG注释宏后表示自己手动添加,放在之间是系统自动添加的)
ON_COMMAND(IDM_HELLEO,OnHello);
⑶原文件中添加函数体:
void CMainFrame::OnHello()
{
MessageBox(“Hello clicked”);
}
电话本示例程序
关键代码:
1、创建基于单文档工程Draw
在CDrawView类中添加成员变量:
private:
CString m_szText;//存输入字符
int m_nIndex;//回车计数
CMenu m_menu;//用于子菜单和菜单项的创建
public:
CStringArray m_strArray;//存储字符串,设为public是为CMainFrame类能够调用它
2、构造函数中初始化:
m_nIndex = -1;
m_szText = “”;
3、CDrawView类的OnChar函数中:
CClientDC dc(this);
if(0x0d == nChar)
{
if(0 == m_nIndex)
{
m_menu.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenu(MF_POPUP,
(UINT)m_menu.m_hMenu,”PhoneBook”);
GetParent()->DrawMenuBar();
}
m_menu.AppendMenu(MF_STRING,ID_PHONE1,
m_szText.Left(m_szText.Find(‘ ‘)));
m_strArray.Add(m_szText);
m_szText.Empty();
Invalidate();
}
else
{
m_szText += nChar;
dc.TextOut(0,0,m_szText);
{
析:
①以前的示例在OnCreate函数中添加代码,OnCreate函数用于窗口的创建,再此对菜单栏的修改会立刻显示,但是窗口创建并显示完成之后,再去更改程序菜单中内容,需要对菜单栏进行重绘操作。
CWnd类的DrawMenuBar成员函数用来完成菜单栏的重绘操作。
②视图没有菜单栏,对菜单栏的操作是在主框架类中,GetParent()获得视图父窗口(框架类窗口)
③回车后字符串清空,屏幕上的字符也要清除,用到Invalidate()
3、还要为创建的菜单项添加消息响应函数,技巧是:
在【帮助】子菜单后添加一系列菜单项,通过类向导添加消息响应函数,然后修改其与动态创建的PhoneBook下的菜单项关联。
注意将函数声明和消息映射都放到注释宏外。
4、在CMainFrame中重写OnCommand虚函数。
OnCommand虚函数会把命令消息截获。这里我们只处理菜单项关联的命令函数,其余的仍交给基类路由。
代码:
int MenuCmdID = LOWORD(wParam);
CMenuView* pView = (CMenuView*)GetActiveView();
if(MenuCmdID >= IDM_PHONE1 && MenuCmdID <
IDM_PHONE1+pView->m_strArray.GetSize())
{
CClientDC dc(pView);
dc.TextOut(0,0,m_strArray.GetAt(MenuCmdID - IDM_PHONE1);
return TRUE;
}
析:
①LOWORD宏取得当前消息的命令ID
②要用到视图类CMenuView的m_strArray成员变量,要先获得视图对象。
CView* GetActiveView() const;
返回一CView类型指针,程序需要CMenuView类型指针,要强制类型转换。
③在框架类中用到视图类类型,要在CMainFrame源文件中包含视图类头文件。
#include “MenuView.h”
另外,MenuView.h中引用了尚未定义的CMenuDoc类(CMenuView.cpp引用MenuView.h之所以没有问题,是因为:
#include “MenuDoc.h”
#include “MenuView.h”
即在引用MenuView.h前引用了MenuDoc.h,提前获得了CMenuDoc类的定义。)
我们可以吧MenuDoc.h的引用提前到MenuView.h中,剪切过去即可。
④return TRUE;的使用是因为:CMainFrame处理过命令消息后还是会把它交给基类,由基类(CFrameWnd)的OnCommand函数继续路由。CFrameWnd会把它交给程序的视图类:CMemuView。return TRUE;后就不会路由了。
未知:必须手动添加命令消息响应?
如何给添加的每个菜单项添加?
P182有一点未看懂