Lesson5:菜单
菜单是用户交互界面中一个重要的角色,本文主要讲解了菜单的使用方法,包括解析MFC的Frame,App,Doc,View类对菜单的响应顺序,菜单的基本操作和右键弹出菜单。
1. MFC的类对菜单命令响应顺序
在MFC的资源里面有一个菜单项,可以在这里添加菜单。菜单设置里Pop-up类型的菜单成为弹出式菜单,这种菜单不能响应命令,一般默认顶层菜单都是弹出式菜单,它们会有子菜单,子菜单可以响应命令。当我们添加了菜单按钮并给了标识IDM_TEST后,在向导里的命令里选择与这个ID对应的命令消息,然后编辑代码。这里通过产生消息框进行测试。
void CMenuView::OnTest()
{
//TODO: 在此添加命令处理程序代码
AfxMessageBox("ViewClicked!");
}
上面的测试是在View类里的,那么对于同一个菜单,当在MFC的Frame,App,Doc,View类里分别添加函数后,事实上响应的先后顺序是View,Doc,Frame,App。我们看看MFC的消息分类。
【消息的分类】
①标准消息:除WM_COMMAND之外,所有以WM_开头的消息(窗口重绘的WM_PAINT消息,窗口创建的WM_CREATE,键盘按键WM_CHAR消息等)。从CWnd派生的类,都可以接收到这类消息。
②命令消息:来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。
③通告消息:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收到这类消息。
从从CWnd派生的类,可以接收标准消息、命令消息、通告消息。从CCmdTarget派生的类,可以接收命令消息、通告消息。
【菜单命令消息的路由过程】 当点击菜单项时,框架类首先接收到这个消息,并把消息交给它的子窗口,view类,由视类进行处理。view类首先查看自己是否有成员函数处理此消息,如果能处理,就由调用相应的响应函数来处理,如果没有,由view类交给doc类处理,doc查找自己能否处理,如果能处理,就由调用相应的响应函数来处理,如果没有,就交还给view类,view类又交还给frame类。这时frame类查看自己能否处理此消息,如果能则处理,不能则交给应用App程序类来处理。
2. 菜单相关操作
对菜单进行修改是在frame框架类窗口的OnCreate函数里进行的。下面是一系列的菜单操作。
int CMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)
{
if(CFrameWnd::OnCreate(lpCreateStruct) == -1)
return-1;
if(!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP |CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("未能创建工具栏\n");
return-1; // 未能创建
}
if(!m_wndStatusBar.Create(this))
{
TRACE0("未能创建状态栏\n");
return-1; // 未能创建
}
m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
//TODO: 如果不需要可停靠工具栏,则删除这三行
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
/*************1.标记菜单*****************/
GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED); //逐层次访问子菜单(文件 0)的菜单项(新建 0)
//GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND | MF_CHECKED); //以命令ID访问
/**************** 2.创建默认菜单*******************/
//对于一个菜单的默认菜单,只能有一个,当设置多个的时候就只有一个生效
GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE); //设置默认的菜单项(表现为加粗)。第一个参数是依赖于第二个参数,如果第二个参数是false,第一个参数用命令的ID,如果第二个参数是true,则第一个参数是位置索引,分割栏也占一个索引
//GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN,FALSE); //其中,false是默认的,可以不写
//GetMenu()->GetSubMenu(0)->SetDefaultItem(5,TRUE); //设置默认的菜单项时,如果用索引号,注意分栏也占一个号
/*******************3.创建图形标记菜单************/
CStringstr; //以下三行是为了获得系统图标的大小,然后调整图标大小,使图标显示适当
str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str); //标记的大小
//CBitmap m_bitmap 作为一个成员函数,写在成员变量里,不在这
m_bitmap.LoadBitmap(IDB_BITMAP1); //下载一副自己制作的位图
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION, &m_bitmap, &m_bitmap); //将位图设置在菜单项前
/**********************4.禁用菜单******************/
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION|MF_DISABLED|MY_GRAYED); //(不能用时自动变灰)在设置同时,需要在frame的构造函数中m_bAutoMenuEnable = FALSE;
/*以下是移除一个菜单后加载一个菜单*/
//SetMenu(NULL); //当参数为NULL时,菜单被移除
//CMenumenu; //定义一个菜单,如果用一个局部变量menu,可以用SetMenu设置菜单,但后面用要menu.Detach
//menu.LoadMenuA(IDR_MAINFRAME); //加载一个菜单
//SetMenu(&menu); //设置一个菜单 调用setmenu后一定要紧接着用菜单对象的detach将菜单句柄与菜单对象分开
//menu.Detach(); //将菜单句柄和c++对象断开,防止窗口重绘时候发生错误,menu对象析构时不会销毁菜单
return0;
}
3. 右键弹出菜单
①在资源中增加一个menu,并对菜单的每一个子菜单设置caption 和ID号。
②增加鼠标右键点击的消息响应OnRButtonDown。
③对子菜单编写命令处理函数,。
void CMenuView::OnRButtonDown(UINT nFlags,CPoint point)
{
//TODO: 在此添加消息处理程序代码和/或调用默认值
CMenumenu; //定义一个菜单
menu.LoadMenu(IDR_MENU1); //加载一个菜单资源
CMenu*pPopup = menu.GetSubMenu(0); //获取一个子菜单
ClientToScreen(&point); //将客户区坐标转换为屏幕坐标
pPopup->TrackPopupMenu(TPM_LEFTALIGN| TPM_RIGHTBUTTON, point.x, point.y, this); //显示菜单
//TrackPopupMenu第一个参数表示菜单显示的位置,第二三参数表示鼠标点击的位置,第四个表示菜单的拥有者,第五个是默认的,指示了一个区域,当点击区域外面时,菜单无效
//当地四个选项说明拥有者后,菜单的子菜单的拥有者也是它
CView::OnRButtonDown(nFlags,point);
}