一:创建静态菜单
1.静态创建一个菜单是一种所见即所得的方式创建的,通过Resource View→Menu→IDR_MAINFRAME的方法创建。
创建可弹出的子菜单时,菜单ID号为空,勾选Pop_up按钮,并且弹出菜单不能进行命令响应。
对创建的子菜单进行命令响应,响应顺序为:CMenuView,CMenuDoc,CMainFrame,CMenuApp。这和命令消息路由过程顺序是一样的。
CMenuDoc和CMenuApp未从CWND派生,故响应函数中不能用MessageBox,要用应用程序框架类
AfxMessageBox。
2.消息分类:
标准消息:
除WM_COMMAND之外,所有以WM_开头的消息。从CWnd派生的类,都可以接收到这类消息。
命令消息:
来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。
通告消息:
由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收到这类消息。
CWnd由CCmdTarget类派生而来,故CWnd类可以接受所有的消息。
3.命令消息路由:
→ OnCommand
AfxWndProc→AfxCallWndProc→WindowProc→OnWndMsg →OnCmdMsg
→OnNotify
由OnCommand完成命令消息的路由,OnNotify完成由控件产生的的路由。
顺序为:CMainFrame首先交由View类,看是否有响应函数,若没有,传递给Doc类,
如果Doc类也没有的话返回给CMainFrame类,它检查自己是否有响应函数,没有的话再传递给App类进行消息路由。
4.创建标记菜单(打√)
菜单的结构相当于楼房,楼层相当于子菜单,而房间相当于菜单项。
利用GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED)。
5.缺省的菜单项(粗体显示)
一个子菜单中缺省菜单项只能有一个,分隔符也算一个索引位置。
6.创建图形标记菜单
需要先获取位图尺寸。(GetSystemMetrics)
所需函数为SetMenuItemBitmaps。
用Format函数时出现错误:代码如下
CString str;
str.Format("d% ,d%",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
终于找到答案了,原来是低级错误,是%d不是d%,呵呵。
7.使得菜单无效
EnableMenuItem
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
nEnable
Specifies the action to take. It can be a combination of MF_DISABLED, MF_ENABLED, or MF_GRAYED, with MF_BYCOMMAND or MF_BYPOSITION. These values can be combined by using the bitwise OR operator. These values have the following meanings:
但是对应的菜单项仍旧可以使用,参考MSDN 有一注意点:
// NOTE: m_bAutoMenuEnable is set to FALSE in the constructor of
// CMainFrame so no ON_UPDATE_COMMAND_UI or ON_COMMAND handlers are
// needed, and CMenu::EnableMenuItem() will work as expected.
但是改成FALSE后MFC将不采用命令更新机制,所以原先哪些不能使用的菜单项现在将变为可以使用了。
8.取消菜单
SetMenu(NULL);
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
//BOOL SetMenu( CMenu* pMenu );
//pMenu
//Identifies the new menu. If this parameter is NULL, the current menu //is removed。
这里会有个错误,当打印的时候。因为menu是个局部对象,有2中方法可以解决:①添加成员变量②调用Detach函数。
9.命令更新机制:
菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。我们可以通过手工或利用ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息。
在后台所做的工作是:操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管。它创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。
更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。
在创建的窗口中,要使菜单项与工具栏相关联,只要使得他们的ID号相同即可。
在ToolBar中。
使用命令更新机制,如果是关联的话,菜单项与工具栏会一起变化,前提是用的是ID索引,因为用MF_BYPOSITION的话菜单项与工具栏的索引不是一一对应的。
void CMainFrame::OnUpdateFileOpen(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
if(ID_FILE_OPEN==pCmdUI->m_nID) //m_nID保存了与当前对象相关联的
pCmdUI->Enable(false); //ID
}
还有就是m_nIndex保存的是与当前对象相关联的的索引值。
10.右键弹出菜单
通过Project→Add To Project→Components and Controls→Pop-up Menu此方法在View类中添加了函数OnContextMenu。
还可以同过捕获右键单击消息进行命令响应。对右键菜单的菜单项进行响应时,先响应View类,TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,GetParent(),NULL);函数TrackPopupMenu的第一个参数改变时貌似没什么反应,第四个参数如果是NULL,则只能响应View类的响应,而如果是GetPareent(),则可以响应MainFrame类。如果两个均有响应函数,则,率先响应view类的。
11.动态添加、插入、删除菜单:
动态添加菜单可在CMainFrame的OnCreate里面进行,利用CMenu的AppendMenu但注意It appends a new item to the end of a menu.如要添加至所需位置,则用InsertMenu。
两者可混合使用,创建时要先CreatePopupMenu,然后再获得菜单栏指针进行插入,然后就可以直接用menu对象调用插入函数。
CMenu menu;
menu.CreatePopupMenu();
GetMenu()->InsertMenu(1,MF_POPUP|MF_BYPOSITION,(UINT)menu.m_hMenu,"Rockets");
menu.Detach();//不要忘了Detach;
如果是先获得GetMenu(),则插入的是一个子菜单,非菜单项,如要插入菜单项,则用menu直接调用,如menu.InsertMenu(1,MF_STRING,201,"Yao");
也可以对刚动态插入的菜单插入菜单项:GetMenu()->GetSubMenu(1)->AppendMenu(MF_STRING,202,"Lowary");
AppendMenu 的第二个参数:Specifies either the command ID of the new menu item or, if nFlags is set to MF_POPUP, the menu handle (HMENU) of a pop-up menu. The nIDNewItem parameter is ignored (not needed) if nFlags is set to MF_SEPARATOR.网上说可以随便写不知道是不是这样。
删除子菜单:
要删除一个子菜单,需要先获得该子菜单对象的指针,再调用DeleteMenu。
为动态添加的菜单项添加命令响应:FileView→Resource.h→#define IDX_XXXX XXX。
然后手动添加响应代码,不会的可以通过MFC ClassWizard添加,然后效仿这个自己添加(3处),不过有一个问题afx_msg int OnMac();函数声明和函数响应对应的修饰符要一样。int CMainFrame::OnMac()
{
MessageBox("Mac");
return 1;
}
如果是int,则都为int 如果是void,则都是void。
12.电话本程序
首先用View类捕获经键盘输入的消息,添加OnChar的命令响应。以下为代码
if(13==nChar)
{
if(0==++m_nindex)
{
// 第一次按Enter键,添加弹出按钮
m_menu.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)m_menu.m_hMenu,"Tel");//在View类中添加子菜单时要先获取父类窗口的指针。
GetParent()->DrawMenuBar();//窗口重绘
} m_menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nindex,m_strLine.Left(m_strLine.Find(' ')));//如果不是第一次,则添加菜单项。
m_strArray.Add(m_strLine);
m_strLine.Empty();
Invalidate(); //将客户区粉刷
}
else
{
m_strLine+=nChar;
dc.TextOut(0,0,m_strLine);//输出
}
然后添加消息响应,可以通过静态添加,然后删除的方法,不过需要将消息映射那里的ON_COMMAND拿到宏外面,编译器检测到你添加的已删除他也会自动帮你删除的。添加消息响应时可以先将输入储存到CStringArray中,然后调用其函数。
13截获本应由View类响应的消息,改为由父类CMainFrame类响应。由于OnCommand是虚函数,所以可以添加OnCommand函数覆盖基类的OnCommand。
virtual BOOL OnCommand(
WPARAM wParam,
LPARAM lParam
);
wParam
The low-order word of wParam identifies the command ID of the menu item, control, or accelerator. The high-order word of wParam specifies the notification message if the message is from a control. If the message is from an accelerator, the high-order word is 1. If the message is from a menu, the high-order word is 0.
所以需要取得低字节的两位,使用LOWORD函数。
int CommandId=LOWORD(wParam);
CMenu3View *pView=(CMenu3View*)GetActiveView();
if(CommandId>=IDM_PHONE1 && CommandId<IDM_PHONE1+pView->m_strArray.GetSize())
{
CClientDC dc(pView);
dc.TextOut(0,0,pView->m_strArray.GetAt(CommandId-IDM_PHONE1));
//MessageBox("ok");
return true;
}
取得对应子类View的指针,用GetActiveView函数,CFrameWnd::GetActiveView
Call this member function to obtain a pointer to the active view (if any) attached to a frame window (CFrameWnd).
CView* GetActiveView( ) const;
用pView->m_strArray.GetSize()这个是为了使菜单具有更好的伸缩性。
return true;
使得命令消息不再向View类传递……
附:要添加级联菜单只要在该菜单项上勾选PopUp按钮就行。
在Caption中添加&x,表示可以用助记键x来按下这个按钮,快捷键打开菜单响应在ResourceView中的Accelerator中的IDR_MAINFRAME中添加。
OVER!