Windows程序设计__孙鑫C++Lesson6《菜单操作》
本节要点
1.消息响应顺序
2.消息分类
3.菜单结构与特点
4.静态菜单的操作
5.动态创建菜单的操作
6.电话本实现
//**************************************************************************************************************
1.消息响应顺序
同时在四个类中添加Test类的响应函数菜单项响应顺序:View类 Doc类 CMainFrame类 App类
2.消息分类
标准消息:除了WM_COMMAND之外,所有以WM_开头的消息;从CWnd派生的类都可以接受这类消息如WM_CHAR ,也可以接受命令消息和通告消息。
命令消息:CCmdTarget派生的类如CWinApp类可以接受命令消息,但不可以接受标准消息.用ON_COMMAND宏映射的.
通告消息:控件产生的消息(如按钮单机)向其符窗口通知事件的发生;通知消息也是以WM_COMMAND形式呈现。CCmdTarget派生的类都可以接受这类消息。
总之,CCmdTarget派生的类都可以接受命令消息和通告消息;CWnd派生的类才可以接受标准消息,当然也可以接受命令消息和通告消息。
消息分类也可参见下图:
3.菜单结构与特点
(1)子菜单:一整栋楼对应整个菜单栏,每个楼层对应菜单栏上一个子菜单(文件、编辑、查看等),房间对应每个子菜单的菜单项。
菜单项: 找整栋楼-->找楼层--->找房间
对应于 找整个菜单栏--->找子菜单--->找菜单项
菜单与楼栋的类比参见下图:
(2)索引访问菜单项不能忽略菜单分隔符,它在索引中也算一个值
(3)命令更新机制:
菜单项的维护是依赖于CN_UPDATE_COMMAND_UI小溪,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象(CCmdUI是一个没有基类的类)。
该对象与菜单项关联,调用该对象的成员函数DoUpdate().注意更新命令UI处理程序仅能应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。
(4)菜单中的索引和工具栏上的索引可能不一致,引起Enable的错误。
4.静态菜单的操作
(1)创建位图菜单 获取系统中菜单位图的大小,然后设定新建位图的大小为合适大小
//**********************************************************************************
CString strMenuInfo;
strMenuInfo.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(strMenuInfo);
m_bitChecked.LoadBitmap(IDB_BITMAP1);
m_bitUnChecked.LoadBitmap(IDB_BITMAP2);
pSuMenu->SetMenuItemBitmaps(2,MF_BYPOSITION,&m_bitChecked,&m_bitUnChecked);
//**********************************************************************************
(2)标记菜单
//**********************************************************************************
//获取整个菜单或子菜单指针
CMenu* pMenu=GetMenu();
CMenu* pSuMenu=pMenu->GetSubMenu(0);
//标记菜单
//pSuMenu->CheckMenuItem(0,MF_BYPOSITION|MF_CHECKED);//位置索引
//pSuMenu->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND|MF_CHECKED);//ID索引
//**********************************************************************************
(3)缺省菜单 缺省菜单只能一个.
//**********************************************************************************
//pSuMenu->SetDefaultItem(1,TRUE);//设置默认菜单项 通过位置计算
// pSuMenu->SetDefaultItem(ID_FILE_OPEN);//设置默认菜单项 ID设定
//**********************************************************************************
(4)菜单不可用:注意设置 m_bAutoMenuEnable 为false,屏蔽系统的自动更新。
执行m_bAutoMenuEnable =FALSE;后系统将不再判断哪些菜单可用或不可用,剪切复制菜单不再变灰.
(5)取消菜单 SetMenu(NULL);
(6)加载菜单
SetMenu(NULL);
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();//将局部菜单与对象断开 由窗口销毁时才析构菜单对象
(5)右键菜单
增加弹出菜单应该到View类而不是MainFrame类。
可以使用增加控件的方法,也可以用TrackPopupMenu函数自己写代码。
自己写代码时注意菜单显示位置为屏幕坐标为准而当前点击时以view类坐标为基准,那么屏幕坐标和客户区坐标就应该转换。坐标装换使用函数ClientToScreen();
弹出菜单的拥有者是View类,那么只能是View类响应弹出菜单项的命令;要想框架了响应应该用GetParent()获取框架指针。当使用GetParent()函数时依然是View类先响应,说明子窗口响应
优先级比父窗口高。
//*********************************************************************************************
void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CMenu menu;
menu.LoadMenu(IDR_MENU_POPUP);
CMenu *pMenu=menu.GetSubMenu(0);
//CRect myRect;
//GetClientRect(&myRect);
ClientToScreen(&point);//坐标转换
pMenu->TrackPopupMenu(TPM_RIGHTALIGN,point.x,point.y,this,NULL);
CView::OnRButtonDown(nFlags, point);
}
//*********************************************************************************************
5.动态创建菜单的操作
1.子菜单或菜单项的追加、插入、删除
//*********************************************************
CMenu menu;
menu.CreatePopupMenu();//创建PopUpMenu
//GetMenu()->AppendMenu(MF_POPUP|MF_ENABLED|MF_STRING,UINT(menu.m_hMenu),"AppendMenu");//添加弹出菜单到主菜单
GetMenu()->InsertMenu(2,MF_BYPOSITION|MF_POPUP|MF_STRING,UINT(menu.m_hMenu),"AppendMenu");
menu.AppendMenu(MF_STRING,IDM_MENUTEST,"Item1");
menu.AppendMenu(MF_STRING,112,"Item2");
menu.AppendMenu(MF_STRING,113,"Item3");//添加菜单项到弹出菜单
menu.Detach();//不要忘记与menu对象分离
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"ItemAppend");//在末尾追加菜单项
GetMenu()->GetSubMenu(0)->InsertMenu(1,MF_BYPOSITION|MF_STRING,115,"ItemInsert");//指定位置插入菜单项
GetMenu()->DeleteMenu(1,MF_BYPOSITION);//删除子菜单
GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);//删除菜单项
//*********************************************************
(2)动态添加的菜单,命令消息的响应
添加ID
添加命令消息响应原型
添加消息映射宏
添加消息响应函数
6.电话本实现
(1)思路:添加菜单并重画菜单栏 调用DrawMenuBar()并且注意是利用CMainFrame指针调。
添加字符输入处理WM_CHAR 消息
重绘窗口Invalidate()
CStringArray实现输入保存(CObArray使用类同).
(2)error C2501: 'CMenu2Doc' : missing storage-class or type specifiers错误解决方法:包含Menu2Doc.h到Menu2View.h
(3)命令消息的路由:
CWnd::WindowProc--->OnWndMsg--->判断消息 标准消息则通过消息映射处理;如果是命令消息交由OnCommand处理,通告消息交由OnNotify处理.最后都交由OnCmdMsg处理。本程序
CMainFrame-->子窗口View类--->Doc类-->文档类查看消息映射,没有则把消息交回给View-->View类查看消息映射,没有则把消息交回给交回CmainFrame--->CMainFrame类查看自己有没有响
应该消息,没有则交给CMenuApp;
命令消息路由过程参见下图:
为了加深理解课参看以下代码:
//*********************************************************
//WINCORE.CPP
virtual LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
virtual BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam,LRESULT* pResult);
virtual BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam);
virtual BOOL CWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, except for DefWindowProc call
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
// special case for commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
// special case for notifies
if (message == WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
//***后面省略****
}
//*********************************************************
virtual BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam);
由此可见在CMainFrame添加virtual BOOL OnCommand()函数则MFC先调用MainFrame的OnCommand函数,
然后再调用基类的OnCommand()函数,这个过程中好像MainFrame先截获了命令消息而View类则无法捕获了。
(4)电话本实现
成员变量:
CMenu2View 类中
public:
CStringArray m_strArray;//保存输入
private:
int m_nIndex;
CMenu m_menu;
CString m_strPhone;
//*********************************************************
//输入实现
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.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenu(MF_POPUP|MF_ENABLED|MF_STRING,UINT(m_menu.m_hMenu),"PhoneBook");
GetParent()->DrawMenuBar();//重绘菜单栏 立即更新
}
//利用输入信息 动态添加菜单项
m_menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nIndex,m_strPhone.Left( m_strPhone.Find(' ') ) );
Invalidate();
m_strArray.Add(m_strPhone);
m_nIndex++;
m_strPhone.Empty();
}
else
{
m_strPhone+=nChar;
dc.TextOut(0,0,m_strPhone);
}
CView::OnChar(nChar, nRepCnt, nFlags);
}
//*********************************************************
//输出实现
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
//主框架消息截获(利用消息路由机制)
int MenuID=LOWORD(wParam);
CMenu2View *pView=(CMenu2View *)GetActiveView();
if(MenuID>=IDM_PHONE1 && MenuID<( IDM_PHONE1 +pView->m_strArray.GetSize()) )
{
CClientDC dc(pView);
dc.TextOut(0,0,pView->m_strArray.GetAt(MenuID-IDM_PHONE1));
MessageBox("MainFrame Responsed!");
return true;
}
return CFrameWnd::OnCommand(wParam, lParam);
}
//*********************************************************
程序运行效果如下图:
本节小结:
1.消息分类
2.消息路由
3.静态菜单和动态创建菜单的操作