6.3.4禁用菜单项
利用CMenu类的成员函数:EnableMenuItem来完成,能够使用、禁用和变灰显示菜单项。
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
第一个参数由第二个参数决定。第二个参数;
MF_BYCOMMAND:指定第一个参数是菜单项标识ID。
MF_BYPOSITION:指定第一个参数是菜单项的位置索引。
MF_DISABLED:禁用菜单项,用户不能选择菜单项,但是菜单项没有变灰。
MF_GRAYED:禁用菜单项,用户不能选择菜单项,但是菜单项变灰。
MF_ENABLED:菜单项可用。
下面在框架类的OnCreate函数中禁用文件->打开菜单项。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{....
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION|MF_DISABLED);
return 0;
}
运行发现并没有禁用打开菜单项。
原因:菜单项的更新都是由MFC的命令更新机制完成的,如果想自己更改菜单项的状态,就需要在框架类的构造函数中把m_bAutoMenuEnable这个变量初始化为FALSE。
运行后发现:编辑子菜单下菜单项不再加灰显示了,因为菜单项的更新现在不是由MFC的命令更新机制完成的。
6.3.5移除和装载菜单
移除菜单:CWnd类SetMenu成员函数来实现。
BOOL SetMenu( CMenu* pMenu );
参数指向一个新菜单对象,若为NULL,表示移除当前菜单。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{....
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION|MF_DISABLED);
SetMenu(NULL);
return 0;
}
运行,菜单栏就消失了。
装载一个菜单资源并显示:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{....
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION|MF_DISABLED);
SetMenu(NULL);
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
return 0;
}
有个异常:menu是一个局部变量
第一种解决方案,将它改成框架类的成员变量。
第二种:在SetMenu函数将此对象设置为菜单之后,利用CMenu类成员函数Detach函数,将菜单句柄和菜单对象分离。这样局部菜单对象生命周期结束后,它不会销毁一个它不具有拥有权的菜单。
menu.Detach();
6.3.6MFC菜单命令更新机制
菜单项状态的维护依赖于CN_UPDATE_COMMAND消息。
让编辑子菜单下的剪切菜单项变为可用状态:
注释掉之前代码。
在ClassWizard中,选择ID_EDIT_CUT,右边选择UPDATE_COMMAND_UI消息。
Add Function->Edit Code
这样在框架类的消息映射中添加了一个ON_UPDATE_COMMAND_UI宏:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTest();
afx_msg void OnUpdateEditCut(CCmdUI* pCmdUI);
//}}AFX_MSG
UPDATE_COMMAND_UI消息的OnUpdateEditCut消息响应函数:
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
}
CmdUI指针类型的参数:决定菜单是否使用、标记以及改变菜单项的文本。
MFC提供命令更新机制,在程序中实现菜单项的可用、禁用,只需要捕获UPDATE_COMMAND_UI消息,在该消息的响应函数中调用CCmdUI对象的响应函数:Enable,SetCheck,SetText函数。
virtual void Enable( BOOL bOn = TRUE );
参数为TRUE表示使菜单项可用,FALSE表示禁用菜单项。默认是TRUE。
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable();
}
运行,剪切菜单项可以使用了。
然后发现工具栏上面的剪切按钮也可以使用了。然后发现二者的标识都是ID_EDIT_CUT,如果要把工具栏上工具按钮和菜单项相关联,只需要将二者的ID设置为同一个标识就可以了。
CCmdUI类有一个m_nID成员变量,用于保存当前菜单项、工具栏按钮,或者其他CCmdUI对象表示的UI对象标识。
例如,在使剪切菜单项可用之前,可以判断一下当前是否是剪切菜单项:
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
if(ID_EDIT_CUT==pCmdUI->m_nID)
{
pCmdUI->Enable();
}
}
CCmdUI类有一个m_nIndex成员变量,用于保存当前菜单项的位置索引。
例如,在使剪切菜单项可用之前,可以判断一下当前是否是剪切菜单项:
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
if(2==pCmdUI->m_nIndex)
{
pCmdUI->Enable();
}
}
计算菜单项位置索引时,一定要把分隔栏菜单项计算在内。
这是子菜单剪切菜单变为可用了,但是工具栏的剪切按钮还是灰色。
这是因为菜单项和工具栏的位置索引计算方式不同。工具栏剪切的位置索引是4(包括分隔栏)。
所以为了保证二者状态一致,最好采用标识ID进行设置。
6.3.7快捷菜单
经常用到鼠标右键显示快捷菜单。
实现右键快捷菜单功能:Project->Add to Project->Components and Controls。
弹出一个组件和控件库对话框:双击Visual C++ Components->Pop-up Menu,这个就是给派生于CWnd类的窗口添加一个右键菜单。->Insert。
弹出一个对话框:框架窗口接收不了鼠标消息,所以选择CMenuView类;下一行就是快捷菜单的资源ID。
插入了Pop-up Menu组建后,添加了以下两处内容:
第一处:在ResourceView选项卡的Menu分支下多了一个标识为CG_IDR_POPUP_MENU_VIEW的菜单资源。
第二处:为CMenuView类增加了一个函数:OnContextMenu。
在改函数内部调用了一个TrackPopupMenu函数来显示一个快捷菜单:
BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
第一个参数:指定菜单在屏幕上显示的位置
第二、三参数:指定快捷菜单显示位置的X和Y坐标。
第四个参数:快捷菜单的拥有对象,即窗口对象
第五个参数:指定一块矩形区域,区域内快捷菜单显示,否则不显示。
知道了TrackPopupMenu函数来显示一个快捷菜单,我们可以在Menu程序中实现自己的快捷菜单:
1、为Menu程序增加一个新的菜单资源:ResourceView->Menu单击鼠标右键->Insert Menu,这时Menu分支下多了一个IDR_MEBU1的菜单资源,接着就可以添加菜单项了。因为不是弹出菜单,去掉Pop-up。
2、为CMenuView类添加WM_RBUTTONDOWN的消息响应函数,然后参见OnContextMenu函数。
void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu* pPopup=menu.GetSubMenu(0);
pPopup->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
CView::OnRButtonDown(nFlags, point);
}
运行,发现快捷菜单显示好像位置不太对,TrackPopupMenu函数中X,Y都是屏幕坐标,而鼠标单击的是窗口客户区坐标,即以程序窗口左上角为坐标原点。
所以需要把客户区坐标转化成屏幕坐标:ClientToScreen函数,在调用TrackPopupMenu函数之前添加ClientToScreen(&point);这一行代码,这样位置就显示正常了。
3、为Menu程序添加快捷菜单上各菜单项命令的响应函数:
ResourceView->IDR_MENU1->显示菜单项鼠标右键->ClassWizard->Cancel
然后利用ClassWizard分别为CMainFrame类和CMenuView类添加一个响应显示菜单项(IDM_SHOW)的函数OnShow,Message框中选择COMMAND消息。
void CMenuView::OnShow()
{
// TODO: Add your command handler code here
MessageBox("View SHOW");
}
void CMainFrame::OnShow()
{
// TODO: Add your command handler code here
MessageBox("Main SHOW");
}
运行,发现只有视类能对快捷菜单的命令进行响应。TrackPopupMenu函数中this指向视类窗口。如果想让框架类对快捷菜单命令进行响应,可以将代码修改为:
void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu* pPopup=menu.GetSubMenu(0);
pPopup->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,GetParent());
CView::OnRButtonDown(nFlags, point);
}