孙鑫视频教程第六课的主要内容是:介绍如何从静态方面和动态方面去创建一个菜单栏,对菜单栏的各项修改!菜单的工作原理及编写应用,菜单命令消息在MFC框架程序的几个类中的传递顺序和处理过程。标记菜单、缺省菜单的实现原理、图形菜单的实现及常犯错误的分析,GetSystemMetrics的应用,快捷弹出菜单的实现方式及其命令响应函数有效范围(与弹出菜单时所指定的父窗口有密切的关系,最底层的子窗口具有最优先的处理机会)。动态菜单的编写,如何让程序在运行时产生新的菜单项及如何手工为这些新产生的菜单命令安排处理函数,如何在顶层窗口中截获对菜单命令的处理,更进一步掌握CString类的应用。
1.消息的分类与响应:
标准消息除WM_COMMAND之外,所有以WM_开头的消息。从CWnd派生的类,都可以接收到这类消息。命令消息来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。通告消息由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收到这类消息。下面来讲解我们实例的消息响应原理:消息转到OnCommand时,由cmainframe类接收转给cview类,如果cview类中有响应函数就响应,如果没有就交给cdoc类,如果cdoc类有响应函数就响应,如果没有就交回给cview类,再由cview类交给cmainframe类,如果cmainframe类中有响应函数就响应,如果没有就由cmainframe类交给capp类响应!所以整个响应函数的顺序为:Cview类—>CMainframe类—>CDoc类—>CApp类!(PS:弹出窗口不能进行消息响应的,所以我们需要在Menu中的属性中将popup这个栏目的勾踢掉!)
在这里创建一个标记菜单需要的函数有:GetMenu()返回一个指向菜单栏的指针,GetSubMenu()返回一个指向子菜单的指针,还有CheckMenuItem()!创建语句如下:在Cmainframe()的OnCreate()函数中添加如下代码:GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED);//按索引号来访问GetMenu()->GetSubMenu(0)->CheckMenuItem(IDM_TEST, MF_BYCOMMAND | MF_CHECKED);//按ID号访问!b.创建缺省菜单项:(注意在一个子菜单中只可以创建一个缺省的菜单项,而且需要注意索引号的获取,要加上分隔符的索引号数)GetMenu()->GetSubMenu(0)->SetDefaultItem(1, TRUE);GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN);c.创建图形标记菜单项:(在vs2008中与vc6.0中有区别:在vs2008中可以不用获取系统度量函数,而在vc6.0中需要使用到这个函数GetSystemMetrics())m_bitmap1.LoadBitmapW(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bitmap1, &m_bitmap1);d.让菜单项不可使用:这个功能可以通过使用属性中的选项轻松实现,但是使用代码需要注意:你需要在cmainframe函数的构造函数中将m_bAutoMenuEnable变量设置为false,下面的代码才会生效,GetMenu()->GetSubMenu(0)->EnableMenuItem(1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);但是在你将m_bAutoMenuEnable变量设置为false之后,会有另一个问题的产生:原来不可以使用的菜单项变为了可用了,这可以使用f中的方法!e.如何将整个菜单取消:可以直接用函数SetMenu(NULL);下面的代码是又讲菜单调用出来的,不过有一点是需要注意的!CMenu menu;//当这个变量为局部变量的时候,就需要加上menu.Detach()这个函数将该变量与c++对象断开,这样就不会发生析构了!
menu.LoadMenuW(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();f.命令更新机制:菜单项状态的维护是依赖于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处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。这样就可以在不可以使用的菜单项上添加一个消息处理程序即可!如下例:void CMainFrame::OnUpdateEditCut(CCmdUI *pCmdUI)
{
// TODO: 在此添加命令更新用户界面处理程序代码
pCmdUI->Enable();
}这样剪切的菜单项就可以使用了!g.在view类中右键弹出菜单功能的实现:在vs2008中,不可以象vc6.0那样可以手动的添加一个弹出菜单功能,所以就需要我们手动的写代码来实现,在view类中添加一个消息响应函数OnRButtonDown(),还需要添加一个menu资源,代码如下:CMenu menu;
menu.LoadMenuW(IDR_MENU1);
CMenu *Popup = menu.GetSubMenu(0);
ClientToScreen(&point);//由客户区的坐标转换为屏幕坐标!
Popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);//创建一个弹出菜单上面的代码就可以实现在视图类中点击右键弹出菜单的功能!
动态添加菜单需要用到的函数主要有:Createpopmenu(), AppenMenu(),InsertMenu(),DeleteMenu(),这里要区别各个函数的主要功能,不要搞错!第一个函数主要创建一个空的菜单,第二个函数主要是在菜单栏的后面添加菜单,但是在不同的地方,它的功能就会不同,在下面的代码中可以看到,第三个函数的功能主要是在两个菜单之间添加自己需要的菜单!第四个函数是删除一个菜单!他们的实现在不同的地方就有不同的用处,具体的实现看如下代码:在cmainframe()类的oncreate()函数中实现CMenu menu;
menu.CreatePopupMenu();
//GetMenu()->AppendMenuW(MF_POPUP, (UINT)menu.m_hMenu, TEXT("WinSun"));//在现有的菜单后面添加菜单
GetMenu()->InsertMenuW(2, MF_BYPOSITION | MF_POPUP, (UINT)menu.m_hMenu, TEXT("WinSun"));//在现有的菜单之间插入一个菜单!
menu.AppendMenuW(MF_STRING, 111, TEXT("Hello"));//添加菜单项
menu.AppendMenuW(MF_STRING, 112, TEXT("WinSun"));
menu.AppendMenuW(MF_STRING, 113, TEXT("Mybloe"));
menu.Detach();GetMenu()->GetSubMenu(0)->AppendMenuW(MF_STRING, 114, TEXT("welcome"));//在子菜单中添加菜单项
GetMenu()->GetSubMenu(0)->InsertMenuW(2, MF_BYPOSITION | MF_STRING, 115, TEXT("维新"));//在子菜单中插入菜单项!
GetMenu()->DeleteMenu(1, MF_BYPOSITION);//删除菜单
GetMenu()->GetSubMenu(0)->DeleteMenu(1, MF_BYPOSITION);//删除菜单项!
这个电话本的功能主要是:当在view类的窗口上写入一个人的姓名和电话号码,就自己添加姓名到菜单栏上面去,然后当我要点击菜单栏上的子菜单的菜单项的时候,这个人的姓名和电话号码又显示在我们的view类上面,实现的代码如下:void CID063View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)//在view类中添加一个消息响应函数!
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CClientDC dc(this);
if (0x0d == nChar)
{
if (0 == ++m_nIndex)
{
m_menu.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenuW(MF_POPUP, (UINT)m_menu.m_hMenu, TEXT("PhoneBook"));//首先得到父类的指针,再由父类的指针指向子类
GetParent()->DrawMenuBar();//让菜单栏发生重绘!这样我们在view类中创建的菜单才可以显示出来!
}
m_menu.AppendMenuW(MF_STRING, IDM_PHONE1+m_nIndex, m_strLine.Left(m_strLine.Find(' ')));
m_strArray.Add(m_strLine);//将输入的字符串添加到集合类中保存起来!
m_strLine.Empty();//清空字符串
Invalidate();//让整个窗口客户端区域无效!
}
else
{
m_strLine += (TCHAR)nChar;
dc.TextOutW(0, 0, m_strLine);
}CView::OnChar(nChar, nRepCnt, nFlags);
}void CID063View::OnPhone1()//这个是动态添加菜单项的响应函数!
{
// TODO: 在此添加命令处理程序代码
CClientDC dc(this);
dc.TextOutW(0, 0, m_strArray.GetAt(1));
}void CID063View::OnPhone2()
{
// TODO: 在此添加命令处理程序代码
CClientDC dc(this);
dc.TextOutW(0, 0, m_strArray.GetAt(2));
}void CID063View::OnPhone3()
{
// TODO: 在此添加命令处理程序代码
CClientDC dc(this);
dc.TextOutW(0, 0, m_strArray.GetAt(3));
}void CID063View::OnPhone4()
{
// TODO: 在此添加命令处理程序代码
CClientDC dc(this);
dc.TextOutW(0, 0, m_strArray.GetAt(4));
}下面的代码是将cview类的消息响应路径的截断,由cmainframe类来响应,要注意其头文件的包含:
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: 在此添加专用代码和/或调用基类
int MenuCmdId = LOWORD(wParam);//只需要获取一个字节的低字节序就可以
CID063View *pView = (CID063View*) GetActiveView();//获取当前视类的指针
if (MenuCmdId >= IDM_PHONE1 && MenuCmdId < IDM_PHONE1 + pView->m_strArray.GetSize())
{
CClientDC dc(pView);
dc.TextOutW(0, 0, pView->m_strArray.GetAt(MenuCmdId - IDM_PHONE1));
return TRUE;
}return CFrameWnd::OnCommand(wParam, lParam);
}(在vs2008中添加虚函数的方法:在你所需要的类上点击右键,然后选择属性中的重写就可以!)
以上的内容就是孙鑫第六课的主要知识点回顾!