(右键)菜单相关
对话框 利用菜单接受用户命令是一中很简单的交互方法,同时也是一种很有效的方法。通常菜单作为一中资源存储在文件中,因此我们可以在设计时就利用资源编辑器设计好一个菜单。关于使用VC设计菜单我就不再多讲了,但你在编写菜单时应该尽量在属性对话框的底部提示(Prompt)处输入文字,这虽然不是必要的,但MFC在有状态栏和工具条的情况下会使用该文字,文字的格式为“状态栏出说明\n工具条提示”。图33_g1 我们要面临的任务是如何知道用户何时选择了菜单,他选的是什么菜单项。当用户选择了一个有效的菜单项时系统会向应用发送一个WM_COMMAND消息,在消息的参数中表明来源。在MFC中我们只需要进行一次映射,将某一菜单ID映射到一处理函数,图33_g2。在这里我们在CView的派生类中处理菜单消息,同时我对同一ID设置两个消息映射,接下来将这两种映射的作用。 ON_COMMAND 映射的作用为在用户选择该菜单时调用指定的处理函数。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)会使菜单被选择时调用OnCommand1成员函数。 ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜单被显示时通过调用指定的函数来进行确定其状态。在这个处理函数中你可以设置菜单的允许/禁止状态,其显示字符串是什么,是否在前面打钩。函数的参数为CCmdUI* pCmdUI,CCmdUI是MFC专门为更新命令提供的一个类,你可以调用 Enable 设置允许/禁止状态 SetCheck 设置是否在前面打钩 SetText 设置文字 下面我讲解一个例子:我在CView派生类中有一个变量m_fSelected,并且在视中处理两个菜单的消息,当IDM_COMMAND1被选时,对m_fSelected进行逻辑非操作,当IDM_COMMAND2被选中时出一提示;同时IDM_COMMAND1根据m_fSelected决定菜单显示的文字和是否在前面打上检查符号,IDM_COMMAND2根据m_fSelected的值决定菜单的允许/禁止状态。下面是代码和说明:下载示例代码 17K 接下来再讲一些通过代码操纵菜单的方法,在MFC中有一个类CMenu用来处理和菜单有关的功能。在生成一个CMenu对象时你需要从资源中装如菜单,通过调用BOOL CMenu: ![]() CMenu* GetSubMenu( int nPos ) 一位置得到子菜单的指针,因为一个CMenu对象只能表示一个弹出菜单,如果菜单中的某一项也为弹出菜单,就需要通过该函数获取指针。 BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一项,nFlag为MF_SEPARATOR表示增加一个分隔条,这样其他两个参数将会被忽略;为MF_STRING表示添加一个菜单项uIDNewItem为该菜单的ID命令值;为MF_POPUP表示添加一个弹出菜单项,这时uIDNewItem为另一菜单的句柄HMENU。lpszNewItem为菜单文字说明。 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜单,位置由变量nPosition指明。如果nFlags包含MF_BYPOSITION则表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID为nPosition的菜单处。 BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜单,如果nFlags包含MF_BYPOSITION则表明修改nPosition位置的菜单,如果包含MF_BYCOMMAND表示修改命令ID为nPosition处的菜单。 BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于删除某一位置的菜单。如果nFlags包含MF_BYPOSITION则表明删除nPosition位置的菜单,如果包含MF_BYCOMMAND表示删除命令ID为nPosition处的菜单。 BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位图菜单,但这样的菜单在选中时只是反色显示,并不美观。(关于使用自绘OwnerDraw菜单请参考我翻译的一篇文章自绘菜单类) 视图中是没有菜单的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到应用的菜单指针。 最后我讲一下如何在程序中弹出一个菜单,你必须先装入一个菜单资源,你必需得到一个弹出菜单的指针然后调用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )弹出菜单,你需要指定(x,y)为菜单弹出的位置,pWnd为接收命令消息的窗口指针。下面有一段代码说明方法,下载示例代码 17K。当然为了处理消息你应该在pWnd指明的窗口中对菜单命令消息进行映射。 CMenu menu; menu.LoadMenu(IDR_POPUP); CMenu* pM=menu.GetSubMenu(0); CPoint pt; GetCursorPos(&pt); pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this); 另一种做法是通过CMenu::CreatePopupMenu()建立一个弹出菜单,然后使用TrackPopupMenu弹出菜单。使用CreatePopupMenu创建的菜单也可以将其作为一个弹出项添加另一个菜单中。下面的伪代码演示了如何创建一个弹出菜单并进行修改后弹出: 当我们提到动态菜单的实现时,我们通常的做法是使用GetMenu() 函数获取一个Cmenu 类指针,然后调用CMenu 类方法AppendMenu, InsertMenu, ModifyMenu, RemoveMenu 等。本文介绍一种更加简洁的方法,它利用MFC 的消息映像机制及CCmdUI 类方法来实现。 ---- 首先,我们简要说说VC 中MFC 的消息映像。每个Windows 程序员大概都对以前使用的窗口函数WindowProc 记忆犹新,当我们面对各种消息时,我们别无他方,只能使用庞大而机械的switch-case 语句来实现不同的分支选择。在VC5.0 中使用V4.2 版的MFC 基本类库,你将告别switch-case 语句,代之以透明的消息映像。要在一个类中使用消息映像,在类声明中,必须显式的加入宏DECLARE_MESSAGE_MAP: class CMyClass: public CBaseClass { DECLARE_MESSAGE_MAP() } ---- 在类实现中,必须使用两个宏BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP,BEGIN_MESSAGE_MAP 带两个参数:当前类和直接父类: ---- BEGIN_MESSAGE_MAP(CMyClass, CBaseClass) ---- // 消息映像项 ---- ON_COMMAND(ID_APP_ABOUT, OnAppAbout) ---- // 消息映像项 ---- END_MESSAGE_MAP() ---- 消息映像项使用下列基本语法: ---- ON_MessageName(ID, ClassMethod) ---- MessageName 是需要处理的消息,ID 是发送消息的标识符,而ClassMethod 为处理此消息的类方法名。MessageName 是MFC 预定义的,可分为以下三种: ---- 命令消息 ---- 子窗口通知消息 ---- Windows 消息 ---- 共一百多个,用户不必记住它们,因为消息映像可以很简单的利用ClassWizard 加入。处理一个消息的类方法ClassMethod 必须在类定义中声明,且有实现代码。其原型为: ---- Afx_msg return_type ClassMethod(paras table) ---- 类CCmdUI 专门(且仅仅)与ON_UPDATE_COMMAND_UI 消息映像宏配套使用,用于管理菜单(还有工具栏按扭等)的实时状态,如是否变灰,是否加选中标记等。 ---- ON_UPDATE_COMMAND_UI 消息映像宏原型为: ---- ON_UPDATE_COMMAND_UI(Menu_Item_ID, Menu_Proc) ---- ON_UPDATE_COMMAND_UI 消息映像宏将一个菜单项(命令项)和一个更新处理过程联结,从而在适当的时机自动调用此更新处理过程来完成对菜单项状态的更新。 ---- Menu_Item_ID 为菜单项的ID 号,Menu_Proc 为此菜单项的更新处理函数,? 为: ---- afx_msg void Menu_Proc (CCmdUI* pCmdUI) ---- 它带有一个CCmdUI 类指针,使用它可调用CCmdUI 的类方法。与菜单有关的类方法有: Enable(BOOL) 使菜单项有效或无效 SetText(LPCTSTR) 设置菜单项的文本 SetCheck(int) 加上或去掉选中标记“X” SetRadio(BOOL) 加上或去掉选中标记“.” ---- MenuProc 被调用的时机有以下几种情况: ---- 用鼠标选中包含该菜单项的菜单条 ---- 用热键选中包含该菜单项的菜单条 ---- 用快捷键选中与该菜单项在同一菜单条下的任一菜单项 ---- 我们以下面菜单结构为例: Test menu Item One ID_ITEM_ONE Ctrl+1 Item Two ID_ITEM_TWO Ctrl+2 Popup Popup One ID_POPUP_ONE Ctrl+3 Popup Two ID_POPUP_TWO Ctrl+4 ---- 当用鼠标左键点按Test menu 菜单条或按Alt+t 或按Ctrl+1/2/3/4 时,四个菜单项的更新处理过程MenuProc 都将被调用。 ---- 当我们考察上面这个具有嵌套结构的菜单时,我们面临这样一个问题:菜单项Item One/Item Two 的更新函数和Popup One/Popup Two 的更新函数形式上是否一致?当Popup One 和Popup Two 都变灰时Popup 是否自动变灰? ---- 根据MFC 的内部机制,仅仅弹出菜单的第一项应附加一些代码,其余项的形式基本是一致的。也就是说在上例中,除菜单项Popup One 外,其他菜单项更新函数的代码基本一致,即根据条件,简单调用CCmdUI 类方法即可。菜单项Popup One 由于是弹出式菜单Popup 的第一项,它的更新函数在以下两种情况下都会被调用: ---- 当弹出式菜单(Popup)的菜单项(Popup One 和Popup Two)要被绘出时 ---- 当此弹出式菜单即Popup 本身要被绘出时 ---- 第一种情况很好理解,正如我们选中Test menu 而Item One 和Item Two 的更新函数会自动执行一样。第二种情况其实也很自然,因为Popup 和Item One/Item Two 不一样,它没有ID 号,不能添加消息映像项,那么它的状态如何更新呢?于是它的第一项的更新函数被调用,为了区分是不同的调用,它将CCmdUI 的类成员变量m_pSubMenu 设置为不同的值。在第一种情况下,m_pSubMenu 等于NULL, 第二种情况下,m_pSubMenu 不等于NULL。 ---- 以下我们给出一个实际的编程范例。由于篇幅关系,我们仅仅给出一些关键的语句,其余的则一并略去。 ---- 在头文件的类声明中: BOOL m_bItemOne, m_bItemTwo, m_bPopupOne, m_bPopupTwo; //用于决定各个菜单项的状态 protected: afx_msg void OnUpdateMenuitemOne(CCmdUI* pCmdUI); afx_msg void OnUpdateMenuitemTwo(CCmdUI* pCmdUI); afx_msg void OnUpdatePopupOne(CCmdUI* pCmdUI); afx_msg void OnUpdatePopupTwo(CCmdUI* pCmdUI); //各菜单项的更新函数 DECLARE_MESSAGE_MAP() 在源文件中: BEGIN_MESSAGE_MAP(CMyDoc, CDocument) ON_UPDATE_COMMAND_UI (ID_ITEM_ONE, OnUpdateMenuitemOne) ON_UPDATE_COMMAND_UI (ID_ITEM_TWO, OnUpdateMenuitemTwo) ON_UPDATE_COMMAND_UI (ID_POPUP_ONE, OnUpdatePopupOne) ON_UPDATE_COMMAND_UI (ID_ POPUP_TWO, OnUpdatePopupTwo) END_MESSAGE_MAP() void CMyApp::OnUpdatetMenuitemOne (CCmdUI* pCmdUI) { pCmdUI->Enable(m_bItemOne); if(m_bItemOne) pCmdUI->SetText("Item One"); else pCmdUI->SetText("Item One is now disabled"); } void CMyApp::OnUpdatetMenuitemTwo (CCmdUI* pCmdUI) { pCmdUI->Enable(m_bItemTwo); if(m_bItemTwo) pCmdUI->SetText("Item Two"); else pCmdUI->SetText("Item Two is now disabled"); } void CMyApp::OnUpdatePopupOne(CCmdUI* pCmdUI) { if (pCmdUI->m_pSubMenu != NULL) { BOOL b_Popup = m_bPopupOne || m_bPopupTwo; pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex, MF_BYPOSITION | (bEnable ? MF_ENABLED : (MF_DISABLED | MF_GRAYED))); return; } pCmdUI->Enable(m_bPopupOne); } void CMyApp::OnUpdatePopupTwo(CCmdUI* pCmdUI) { pCmdUI->Enable(m_bPopupTwo); } |