(孙鑫 6)菜单

来源:互联网 发布:ireport软件 编辑:程序博客网 时间:2024/05/23 00:10
菜单的工作原理及编写应用,菜单命令消息在MFC框架程序的几个类中的传递顺序和处理过程。标记菜单、缺省菜单的实现原理、图形菜单的实现及常犯错误的分析,GetSystemMetrics的应用,快捷弹出菜单的实现方式及其命令响应函数有效范围(与弹出菜单时所指定的父窗口有密切的关系,最底层的子窗口具有最优先的处理机会)。动态菜单的编写,如何让程序在运行时产生新的菜单项及如何手工为这些新产生的菜单命令安排处理函数,如何在顶层窗口中截获对菜单命令的处理,更进一步掌握CString类的应用。1.添加菜单:在resource中选择menu,在空白菜单框处右键选择属性(编辑)。为了让属性对话框一直在屏幕上易于编辑,我们可点击对话框左上角的图标--保持可见。  弹出菜单popup为复选,不能相应命令,所以ID号不能输入。若取消popup复选则可输入ID相应命令。  标识资源:如IDC IDC IDM IDS,如可设置ID号为IDM_TEST,则打开查看——建立类向导——选择IDM_TEST——消息选择COMMAND,在CMainFrame类中增加一个函数  也可在菜单项test上右键选择建立类向导2.CMenuApp是从CWinApp派生的,不是从CWnd类派生,所以没有CWnd的MessageBox,但有框架类函数AfxMessageBox()(看来第3章还要看一哈),CMenuDoc也是,而CMenuView是CWnd类派生的 可以。  在这四个类中都添加COMMAND消息的处理函数OnTest,结果CView类最先响应。然后是CDoc类响应,再CMainFrame,最后CApp类。3.消息的分类:·标准消息(除WM_COMMAND外,所有以WM_开头的消息),从CWnd派生的类,都可接收到这类消息·命令消息(来自菜单、加速键或工具栏按钮的消息),都以WM_COMMAND呈现。在MFC中,通过菜单项的标识ID来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。&&&从CCmdTarget派生的类都可以接到。·通告消息(由控件产生的消息),如按钮的单击、列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类都可接收者类消息。  CWnd是从CCmdTarget派生出来的。但CCmdTarger不能接收标准消息。CWnd可接收任何消息。4.命令消息的路由  mfc用AfxWndProc替换了窗口过程函数,它调用了AfxCallWndProc,它调用WindowProc函数(CWnd的成员函数),它再调用OnWndMsg判断接收的消息:若是命令消息,就交给OnCommand处理;若是通告消息,就交给OnNotify处理,最后交给OnCmdMsg函数处理。路由过程:MainFrame接收消息,然后交给view类,view查找有没有对此消息的响应,若没有就交给Doc文档类,若也没有响应,就交还给view类,view又交还给MainFrame类,MainFrame类查看有没有响应此消息的,若没有就交给App类。5.创建标记菜单(打钩):可在MainFrame的OnCreate里创建。  菜单的结构像楼层一样,菜单的索引是从0开始的,如3楼二号房间,就是302。一栋楼对应一个菜单栏,每一层对应每一个子菜单,房间对应菜单上的菜单项。(所以子菜单指的是一层楼)。&&  对于0层、1层……对应:从左到右的第1个、第2个子菜单……每个房间都是有标识的,可通过索引去访问。  可用CWnd的CMenu* GetMenu( ) const;  //返回的是CMenu封装了与菜单有关的;即代表了这个CWnd对象的菜单(栏)。  CMenu* GetSubMenu( int nPos ) const;获取子菜单,返回CMenu的指针,需要位置,相当于找到楼层。  UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );  //使菜单项前面有钩或没有当第二个参数是MF_BYCOMMAND,则第一个是菜单的ID号;BYPOSITION就是索引号。也可以是它们的组合(还可以是CHECKED或UNCHECKED)。  设置缺省的菜单项BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE );(缺省菜单项是粗体的)  注意分隔符也是一个菜单项。一个子菜单项中只能有一个缺省菜单项。&&它有什么作用??(双击子菜单就相当于击中了该默认子菜单项)BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );//创建图形标记菜单,nFlags是MF_那些  但在定义CBitmap对象的时候要放到头文件声明中,在private下,成为成员变量,所以用m_开头。  用int GetSystemMetrics(  int nIndex   // system metric or configuration setting to retrieve,如SM_CXMENUCHECK);  //获取系统的信息用CString对象的Format,将对象格式化到str当中。CString str;str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));//这里获得的是系统的菜单check位图的宽度和高度6.使菜单项失效,用CMenu的UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );  第2个参数可以是MF_BYCOMMAND/BYPOSITION/DISABLED/ENABLED/GRAYED,最好将DISABLED和GRAYED一起使用。而MFC所有的消息更新有自己的机制,这里直接使用EnableMenuItem没有效果,msdn说在CMainFrame的构造函数中m_bAutoMenuEnable要被设置为FALSE,这样就不需要ON_UPDATE_COMMAND_UI或ON_COMMAND句柄了。  但它设置为FALSE之后,有些本来灰色按钮也是黑的了。7.将整个菜单取消掉BOOL SetMenu( CMenu* pMenu ); //pMenu指向新的菜单,若是NULL,则当前菜单将被移走。  直接用SetMenu(NULL);//则程序没有菜单了  也可用CMenu对象的LoadMenu去加载菜单BOOL LoadMenu( UINT nIDResource );CMenu menu;menu.LoadMenu(IDR_MAINFRAME);SetMenu(&menu);  但这样会出现一些问题,如可能被非法销毁了。除了弄成成员变量之外,还可以用CMenu对象的Detach函数(分离),即在后面加个menu.Detach;   //局部对象,设置完菜单后,都要用Detach,将它们间的联系断开(和CMenu的)8.命令更新菜单项状态的维护是依赖于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处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。说明:可在classwizard中,选择相应的菜单项,选择UPDATE_COMMAND_UI消息,添加函数void CAboutDlg::OnUpdateEditCut(CCmdUI* pCmdUI) //提供了CCmdUI类{// TODO: Add your command update UI handler code here}CCmdUI类可以SetCheck设置是否有标记,SetText设置项目的文本,Enable使之有效无效如:可使剪切无效,pCmdUI->Enable(FALSE); //要在MainFrame中9.若要让工具栏toolbar上的图标与某菜单一样,只要双击图标,设置ID号一样就可以了  而pCmdUI有个成员变量m_nID存放了菜单项的ID号,测试可用ID_FILE_NEW==pCmdUI->m_nID,而m_nIndex存放了其索引值。而图标和菜单项的ID同,索引值却不同(图标是从左到右,菜单项是从上到下)  所以如果想让某个菜单可以使用或者不能使用,直接在类向导中选择UPDATE_COMMAND_UI创建函数即可。10.弹出菜单功能:选择工程-》增加到工程-》组件和控件,选择VC++ componets,选择pop-up menu插入,要选择插入到CView类,因为它覆盖在MainFrame上。  CView类就有个OnContextMenu函数了:afx_msg void OnContextMenu( CWnd* pWnd, CPoint pos );//此函数在用户按下右键时,由框架调用。还可用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );  //在某处显示弹出菜单,nFlags主要设置弹出的位置,(x,y)鼠标的左边,pWnd表示是哪个对象拥有这个弹出菜单,lpRect指定一个矩形区域,在此区域点右键弹出,此区域之外点击就消失。  先创建一个菜单(在资源工具栏点),弹出时不显示其名称,所以名字可随意写,然后在RBUTTONDOWN消息中写:CMenu menu;menu.LoadMenu(IDR_MENU1);CMenu* pPopup=menu.GetSubMenu(0);pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,this);  但point是客户区左边,而TrackPopupMenu需要的是屏幕坐标,所以需要转换坐标。void ClientToScreen( LPPOINT lpPoint ) const;void ClientToScreen( LPRECT lpRect ) const;  //CWnd对象的,将客户区左边转换为屏幕坐标  但编写菜单项的COMMAND相应代码时,只有view类响应,mainframe不响应,因为弹出菜单的所有者是this。但子窗口比父窗口拥有优先响应权,上面把this指针设为GetParent()即为MainFrame指针时,若子窗口也有命令相应函数的话,还是子窗口view类先相应。1.动态添加菜单:  使用CMenu的BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );  //添加  nFlags表示菜单的状态,如MF_STRING菜单项是字符串,MF_CHECKED,MF_POPUP弹出菜单,MF_SEPARATOR分隔栏  nIDNewItem指示命令ID,弹出菜单(如查看)就用HMENU,而MF_SEPARATOR则忽略。  lpszNewItem表示新菜单项的内容创建弹出菜单:BOOL CreatePopupMenu( );  //创建空的弹出菜单CMenu menu;menu.CreateMenu();menu.Detach();GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"myMenu");  //句柄要转换成UINT要插入到中间的话,用BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );//uFlags可设为MF_BYCOMMAND,MF_BYPOSITION,或者AppenMenu的那些,后面跟它差不多,nPosition是指的哪个位置就插入到哪个位置。可再用AppendMenu添加菜单项,要用参数MF_STRING……可用DeleteMenu删除菜单BOOL DeleteMenu( UINT nPosition, UINT nFlags ); //可以删除菜单或菜单项GetMenu()->GetSubMenu(0)->DeleteMenu(0,MF_BYPOSITION);菜单删除外之后,菜单栏就是白的了,但工具栏还是有效的。2.可在fileview中选择resource.h,在里面添加自定义ID。如:#define IDM_HELLO111添加命令响应函数(3个步骤):1)在头文件做函数原型,如在CMainFrame的头文件中,DECLARE_MESSAGE_MAP()上面,添加afx_msg void OnHello();2)消息映射:在CMainFrame的构造函数上面,在END_MESSAGE_MAP()上面写一个:ON_COMMAND(IDM_HELLO,OnHello)3)写函数:void CMainFrame::OnHello(){……}以上过程用ClassWizard直接可以完成3.在view类输入信息,创建窗口,在系统已经创建了窗口后若菜单栏改变了,则需要重画菜单栏,要用void DrawMenuBar();用void Invalidate( BOOL bErase = TRUE );//重画窗口,使客户区变为无效CString类的Find函数,查找一个字符或字符串,然后返回第一个找到的索引int,最后用Left截取左边部分。定义CString数组时可用CStringArray集合类。可直接用Add增加一个元素,也可用CString GetAt(int nIndex)取出一个元素  创建动态菜单命令响应函数技巧:1)在resource.h中定义菜单ID号2)在资源窗口创建随意创建1/2/3/4/个菜单项,然后用classwizard创建各自的命令响应函数3)然后删除资源窗口的随意创建的菜单项4)为了区分自己添加的命令响应函数,在头文件中将注释宏里的函数移动到注释宏下面去(不影响)5)必须的:在消息映射中,一定要将那些ON_COMMAND拿到下面(防止类向导自动删除了)view类的OnChar函数:CClientDC dc(this);if(0x0d==nChar)  //按回车键{if(0==++m_nIndex)  //第一次按回车键,m_nIndex初始化为-1{menu.CreatePopupMenu();GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"PhoneBook");GetParent()->DrawMenuBar();  //注意要用GetParent()获得MainFrame对象//重画后,菜单栏才可以更新}menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nIndex,m_strLine.Left(m_strLine.Find(' ',0)));m_strArray.Add(m_strLine);  //增加字符串元素,上面添加菜单项,定义了连续的IDM_PHONE宏m_strLine.Empty(); //但已经TextOut的文字并没有擦除Invalidate();}else{m_strLine+=nChar;  //初始化为“”dc.TextOut(0,0,m_strLine);}在连续添加的响应函数中:CClientDC dc(this);dc.TextOut(0,0,m_strArray.GetAt(0));  //输出对应的字符串4.使MainFrame比View类先截获Command消息:  CWnd的OnCommand截获所有的命令消息virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );在MainFrame中添加虚函数,OnCommand……  wParam是4字节的,它的低字节序是菜单的命令ID。取低字节序的函数LOWORD,高字节序的HIWORD.用CFrameWnd的CView* GetActiveView( ) const; //获取框架类的激活的view类  在编辑OnCommand类时用到CMenuView类,要在源文件里包含view类的头文件。BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) {// TODO: Add your specialized code here and/or call the base classint MenuCmdID=LOWORD(wParam);CMenuView *pView=(CMenuView*)GetActiveView();if(MenuCmdID>=IDM_PHONE1 && MenuCmdID<IDM_PHONE1+pView->m_strArray.GetSize()){MessageBox("Test");}return CFrameWnd::OnCommand(wParam, lParam);}但这里响应了mainframe类的消息后还要相应view类的消息。可以在此函数中先return TRUE即可。
0 0
原创粉丝点击