菜单设计 零、引子: 消息的分类: 标准消息:除WM_COMMAND外,所有以WM_开头的消息,从CWnd派生的类都可以接收这类消息。 命令消息:来自菜单项、工具栏按钮的消息,这类消息都以WM_COMMAND呈现,在MFC中,不同的命令消息由不同的ID号来标识,从CCmdTarget类派生出的类都可以接收这类消息。 通告消息:由控件产生的消息,如按钮的单击,为的是向其父窗口通知事件的发生,这类消息也以WM_COMMAND呈现,从CCmdTarget类派生出的类都可以接收这类消息。 (注:CWnd是从CCmdTarget派生出来的子类) 新建一个工程Menu。 一、创建标题菜单: 标题菜单,就是在菜单项前作上标记,比如说打上对号等。 主要涉及到三个函数: GetMenu()//获得指向菜单栏的指针 GetSubMenu(0)//获得指向子菜单的指针,参数为子菜单的索引,从0开始 CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND | MF_CHECKED)//对菜单项进行标记操作,第一个参数由第二个参数决定:如果是第二个参数是MF_BYCOMMAND,则第一个参数用菜单项的标记号(在资源中,查看菜单项的属性即可知);如果第二个参数是MF_BYPOSITION,则第一个参数是索引,从0开始。 具体应用: 在CMenuFrame.cpp中的OnCreate()函数中添加如下语句: GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND|MF_CHECKED); 或是 GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION|MF_CHECKED); 二、创建缺省的菜单项(即相应的菜单项变成了粗体) 要用到BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE )这个函数,第一个参数也是由第二个参数决定的:若第二个参数是FALSE,则第一个参数用菜单项的ID号,且在缺省状态下,第二个参数是FALSE;若第二个参数是TRUE,则第一个参数是菜单项的索引号,从0开始。 在CMenuFrame.cpp中的OnCreate()函数中添加如下语句: GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE); (注意:一个子菜单中只能有一个缺省的菜单项,并且用索引标识菜单项的时候,下拉菜单中的分隔栏也占有一个索引号) 三、创建图形标题菜单 1、获知所需菜单项位图的像素大小 CString str; str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK)); MessageBox(str); 注:GetSystemMetrics()是获得菜单项为位图的像素宽度和高度 CString对象str调用Format()函数是为了格式化所得的数据到字符串中,以便调用MessageBox()进行输出。 2、创建位图菜单 在CMenuFrame类中,添加一个私有变量:CBitmap m_bitmap; 在OnCreate()中添加如下语句: m_bitmap.LoadBitmap(IDB_BITMAP1); GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(1,MF_BYPOSITION,&m_bitmap,&m_bitmap); 注:BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked ); 第一个参数由第二个参数决定:如果是第二个参数是MF_BYCOMMAND,则第一个参数用菜单项的标记号(在资源中,查看菜单项的属性即可知);如果第二个参数是MF_BYPOSITION,则第一个参数是索引,从0开始。 第三、四个参数分别为不选和选中该菜单项时,所显示的位图的指针。 四、屏蔽菜单项(就是我们所见到的变灰): 第一种方法: 在CMainFrame构造函数中,设置m_bAutoMenuEnable = FALSE; 在CMainFrame类的OnCreate()函数中调用如下函数: GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION|MF_GRAYED|MF_DISABLED) 注:UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );//祥看csdn 第二种方法: 通过View—ClassWizard,来增加函数: void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here pCmdUI->Enable(TRUE);//参数缺省的是TRUE,所以可以不用写参数,若是FALSE,则是不可用。 } 五、取消/重现菜单栏: SetMenu(NULL);//参数为NULL的话,该函数就是将菜单栏设置为空,即取消了菜单栏 CMenu menu; menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu);//用当前的菜单资源重现菜单栏 menu.Detach();//将菜单对象与该函数脱离关系,使OnCreate()的析构函数不会销毁menu资源。 六、右键弹出菜单功能 第一种方法:直接调用系统的资源 菜单:Project—Add To Project—Components And Controls—Visual C++ Components—Pop-up Menu,导入到CMenuView类中,就可以了! 第二种方法: 首先,在菜单资源中增加一个菜单资源:IDR_MENU1, 其次,在CMenuView.cpp中的OnRButtonDown()函数中作如下操作: 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);//获得子菜单 ClientToScreen(&point);客户区与桌面坐标系之间的转换 pPopup->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this,0);//第二、三个参数用的是桌面坐标系,所以上面要进行坐标系的转换。 //第四个参数拥有该弹出菜单的框架,this指的是当前窗口 CView::OnRButtonDown(nFlags, point); } 补:对弹出菜单的菜单项进行的操作是通过ClassWizard来实现的 七、动态创建菜单 1、动态连接一个子菜单 在CMainFrame类的OnCreate()函数中添加如下语句: CMenu menu; menu.CreateMenu();//该函数作用是创建一个新菜单,并且和菜单对象建立连接 GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"Piao");//在原来的菜单后面加上新建的菜单。 menu.Detach(); 2、动态插入一个子菜单 只需将上面的第三句改成: GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"Piao"); 第一个参数表示按索引的话,在哪个子菜单前插入该子菜单。 3、子菜单中创建菜单项 menu.AppendMenu(MF_STRING,111,"SUO");//对自己创建的菜单添加菜单项 GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"爱");//对已有子菜单连接菜单项。 GetMenu()->GetSubMenu(0)->InsertMenu(2,MF_BYPOSITION | MF_STRING,115,"邢飘"); //对已有子菜单插入一个菜单项 (注:关键是理解清楚GetMenu()和menu调用AppendMenu()和InsertMenu()的区别。) 4、动态删除一个菜单 GetMenu()->DeleteMenu(1,MF_BYPOSITION);//删除一个子菜单 GetMenu()->GetSubMenu(0)->DeleteMenu(1,MF_BYPOSITION);//删除一个菜单项 八、手动的添加消息响应函数 例:对GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"爱");进行手动添加消息响应函数步骤: ① 在Resource.h中对编号进行宏定义: #define IDM_LOVE 114 ② 在MainFrm.h中,添加如下: protected: //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG afx_msg void OnLove(); DECLARE_MESSAGE_MAP() ③ 在MainFrm.cpp中,在消息映射添加该函数的消息映射: BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND(IDM_LOVE,OnLove)//模仿消息映射中的映射 END_MESSAGE_MAP() ④ 在MainFrm.cpp中,实现OnLove()函数: void CMainFrame::OnLove() { MessageBox("索广宇爱邢飘!"); } 九、利用菜单创建一个电话本 ①在CMenu2View类中创建如下私有变量: CString m_strLine; CMenu m_menu; int m_nIndex; ②在构造函数中进行初始化: m_nIndex=-1; m_strLine=""; ③在CMenu2View类中添加如下函数,用来接收字符消息: void CMenu2View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CClientDC dc(this); if(0x0d==nChar) {//如果是回车键 if(0==++m_nIndex) {//创建子菜单,且只创建一次 m_menu.CreateMenu(); GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)m_menu.m_hMenu,"PhoneBook"); GetParent()->DrawMenuBar();//调用父窗口的窗口重绘函数 } m_menu.AppendMenu(MF_STRING,111,m_strLine.Left(m_strLine.Find(' '))); m_strLine.Empty(); Invalidate();//清空当前DC上的字符串 } else { m_strLine+=nChar; dc.TextOut(0,0,m_strLine); } CView::OnChar(nChar, nRepCnt, nFlags); } ④ 手动添加命令响应函数: 按第八项的步骤添加命令响应函数: void CMenu2View::OnPhone1() { CClientDC dc(this); dc.TextOut(0,0,m_strArray.GetAt(0)); } void CMenu2View::OnPhone2() { CClientDC dc(this); dc.TextOut(0,0,m_strArray.GetAt(1)); } void CMenu2View::OnPhone3() { CClientDC dc(this); dc.TextOut(0,0,m_strArray.GetAt(2)); } void CMenu2View::OnPhone4() { CClientDC dc(this); dc.TextOut(0,0,m_strArray.GetAt(3)); } 拓:在CMainFrame类中实现捕获命令消息 BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class int MenuCmdID=LOWORD(wParam);//wParam的低字节是命令ID号。 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 CFrameWnd::OnCommand(wParam, lParam); }