阅读本文前,我们假设您已经: 1,知道如何创建一个单文档的App Wizard 2,知道C++ 类、函数重载等简单知识 3,知道如何给View类或者Doc文档添加成员变量 4,会用MFC的IDE调试工具最好,那么本文的程序您可以copy去调试 5,知道如何为某个框架类添加虚函数或消息处理函数 一、 消息的分类: 消息的分类:标准消息,命令消息,通告消息。 [标准消息]:除WM_COMMAND之外,所有以WM_开头的消息。 [命令消息]:来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。 在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。 [通告消息]:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。 注意: 1)从CWnd派生的类,都可以接收到[标准消息]、[命令消息]和[通告消息]。 2)从CCmdTarget派生的类,都可以接收到[命令消息]和[通告消息]。 3)CCmdTarget是CWnd的父类 二、 菜单消息传递过程 MFC中菜单项消息如果利用ClassWizard来对菜单项消息分别在上述四个类中进行响应,则菜单消息传递顺序:View类èDoc类èCMainFrame类èApp类。菜单消息一旦在其中一个类中响应则不再在其它类中查找响应函数。 当点击一个菜单项的时候,最先接受到菜单项消息的是CMainFrame框架类,CMainFrame框架类将会把菜单项消息交给它的子窗口View类,由View类首先进行处理;如果View类检测到没对该菜单项消息做响应,则View类把菜单项消息交由文档类Doc类进行处理;如果Doc类检测到Doc类中也没对该菜单项消息做响应,则Doc类又把该菜单项消息交还给View类,由View类再交还给CMainFrame类处理。如果CMainFrame类查看到CMainFrame类中也没对该消息做响应,则最终交给App类进行处理。 三、 菜单指针的获取,及相关设置 在CMainFrame::OnCreate下可以直接实验以下操作 几个相关和重要的函数 CMenu* GetMenu( ) ;//CWnd::GetMenu得到窗口菜单栏对象指针。 CMenu* GetSubMenu( ) ;//CMenu::GetSubMenu获得指向弹出菜单对象指针 UINT CheckMenuItem( );//CMenu::CheckMenuItem Adds check marks to or removes check marks from menu items in the pop-up menu. BOOL SetDefaultItem();//CMenu::SetDefaultItem Sets the default menu item for the specified menu. BOOL SetMenuItemBitmaps( );//CMenu::SetMenuItemBitmaps 设置位图标题菜单。 UINT EnableMenuItem();//CMenu::EnableMenuItem使菜单项有效,无效,或变灰。 BOOL SetMenu( CMenu* pMenu );//CWnd::SetMenu在当前窗口上设置新菜单或移除菜单。 HMENU Detach( );//CMenu::Detach Detaches a Windows menu from a CMenu object and returns the handle. 获取菜单的宽和高: GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK) 例子: 1, 给菜单项打上标记 GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED); GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND | MF_CHECKED); 2, 设置缺省菜单项 GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE); GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN); 3, 图形标记菜单 先创建图形,注意底色不要是白色 m_bitmap.LoadBitmap(IDB_BITMAP1); GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&m_bitmap,&m_bitmap); 4, 屏蔽菜单,使之不能用 (需要在CMainFrame::CMainFrame()中设置m_bAutoMenuEnable=FALSE;) GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION | MF_DISABLED | MF_GRAYED); 5, 取消和加载菜单 用此功能,可以动态的修改菜单 SetMenu(NULL);//取消菜单项 CMenu menu; menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu); menu.Detach();//菜单句柄和对象断开,使对象析构时不销毁菜单 四、 命令更新机制 菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。 在后台操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管并创建一个CCmdUI对象和第一个菜单项相关联,调用对象成员函数DoUpdate()(注:这个函数在MSDN中没有找到说明)发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。此后同一个CCmdUI对象又设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。 更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。 注意:以下两语句的效果不同。对菜单项一样,对工具栏索引对应不一样 if(0==pCmdUI->m_nIndex)pCmdUI->Enable(FALSE); if(ID_FILE_NEW==pCmdUI->m_nID)pCmdUI->Enable(FALSE); 五、 右键弹出菜单 1, Project->Add to Project->Components and Controls添加pop menu即可。 2, 静态添加菜单方法。 1) 在资源里编辑一个菜单 2) View中添加WM_RBUTTONDOWN消息对应函数。 3) 在OnRButtonDown中添加如下 CMenu menu; menu.LoadMenu(IDR_MENU1); CMenu *pPopup=menu.GetSubMenu(0); ClientToScreen(&point); pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,GetParent());//不想在框架类显示就把GetParent()改为this。 4) 给自己编辑的菜单加对应的处理函数(利用Classwizard)。 如果加在CMainFrame 5) 3, 动态添加菜单(子菜单数目变化) 在MainFrame::OnCreate中试验如下 CMenu menu; menu.CreatePopupMenu(); GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"WinSun"); GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"WinSun"); menu.AppendMenu(MF_STRING,IDM_HELLO,"Hello"); menu.AppendMenu(MF_STRING,112,"Weixin"); menu.AppendMenu(MF_STRING,113,"Mybole"); menu.Detach(); 响应函数添加方法: 1) 在resource.h中添加资源ID定义 #define IDM_HELLO 111 2) 在MainFrm.h中声明消息响应。DECLARE_MESSAGE_MAP()之前添加 afx_msg void OnHello(); 3) 在MainFrm.cpp中END_MESSAGE_MAP()之前,添加 ON_COMMAND(IDM_HELLO,OnHello) 4) 在MainFrm.cpp中添加CMainFrame::OnHello()函数定义 4, 给系统菜单添加/删除菜单项 GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"Welcome"); GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING,115,"维新"); GetMenu()->DeleteMenu(1,MF_BYPOSITION); GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);*/ GetMenu()->DeleteMenu( GetMenu()->GetSubMenu(1)->GetMenuItemID(0),MF_BYCOMMAND); 六、 动态添加系统菜单项 电话本程序:键盘输入人名,空格之后电话号码。回车之后,把人名添加到菜单项。当点击菜单时,显示电话本信息。 1, 给View添加private 的 CString m_strLine;//存储输入的字符串 CMenu m_menu;//菜单 int m_nIndex;//用于计数,是不是第一次 2, 添加View的public成员 CStringArray m_strArray; 3, 添加View的WM_CHAR消息响应函数 4, 在View::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,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); } 5, 添加菜单的响应函数 方法一: 1) 在Toolbar上添加一个菜单,其弹出项为ID号为IDM_PHONE1, IDM_PHONE2, IDM_PHONE3。 2) 在Rsource.h中添加 #define IDM_PHONE1 201 #define IDM_PHONE2 202 #define IDM_PHONE3 203 3) 利用ClassWizard给菜单项添加响应函数,并编辑函数 4) 删除菜单项(其响应函数会依然存在,并且4步中利用了ID号,可以对应过去) 方法二: 视频没有提供,但我感觉应该有其他方法。方法一太死板 6, 另:由框架类捕获菜单响应,而不是由View处理,可以节约代码 1) 给MainFrame添加WM_COMMAND消息响应函数 2) 在其中添加如下代码 int MenuCmdId=LOWORD(wParam); CMenu2View *pView=(CMenu2View*)GetActiveView(); if(MenuCmdId>=IDM_PHONE1 &&MenuCmdId<IDM_PHONE1+pView->m_strArray.GetSize()) { CClientDC dc(pView); dc.TextOut(0,0,pView->m_strArray.GetAt(MenuCmdId-IDM_PHONE1)); return TRUE; } 第6点提供的方法,不能解决第5点提出方法的限制,响应菜单还是和事先编好的ID相对应,不能动态增加 其他注意的地方: 1, 弹出菜单(Pop-up)是不能用来作命令响应的。此项设置在菜单属性栏内。 2, 将Toolbar的ID设为菜单的ID,二者即发生了关联。 3, 在计算子菜单菜单项的索引的时候,分隔栏符也算索引。 4, 消息影射函数 Cpp文件中 BEGIN_MESSAGE_MAP(CMenu2View, CView) //{{AFX_MSG_MAP(CMenu2View) //位置1 //}}AFX_MSG_MAP 位置2 END_MESSAGE_MAP() 头文件中 //{{AFX_MSG(CMenu2View) 位置3 //}}AFX_MSG 位置4 DECLARE_MESSAGE_MAP() 消息映射在位置1和位置2不一样,可以调用Classwizard看到修改结果 位置3和位置4的函数声明,随便放1或2都可以,没有影响
|
欢迎以任何形式转载本文,只要对您有用 |
韦伯主页: http://mail.ustc.edu.cn/~bywang(提供此笔记系列相关源程序下载) 韦伯Blog: http://webbery.tianyablog.com |
参考书目和网站: |
(1)孙鑫VC++视频 |
(2)1-6章主要参考: hbyufan的BLog |
(3)11-20章主要参考: songpeng的Blog |