6.1 菜 单 设计
菜单是可视化编程的重要组成部分,是一种方便地给命令分组并访问这些命令的方法。菜单通常用来显示程序的各项功能,以方便用户选择执行,通过对菜单命令进行编程可以调用程序中相应的功能。
6.1.1 菜单类CMenu
在MFC中,CMenu类封装了Windows的菜单功能,它提供了多个方法用于创建、修改、合并菜单。CMenu类的主要方法如下。
(1)Attach方法:该方法用于将句柄关联到菜单对象上。语法如下:
BOOL Attach( HMENU hMenu );
参数说明
返回值:为非零,表示执行成功,否则执行失败。
(2)Detach方法:该方法从菜单对象上分离菜单句柄。语法如下:
HMENU Detach( );
返回值:分离的菜单句柄。
(3)FromHandle方法:该方法根据菜单句柄返回一个菜单对象指针,如果句柄没有关联的菜单对象,则一个临时的菜单对象指针将要被创建。语法如下:
static CMenu* PASCAL FromHandle( HMENU hMenu );
参数说明
返回值:菜单对象指针。
(4)CreateMenu方法:该方法用于创建一个菜单窗口,并将其关联到菜单对象上。语法如下:
返回值:执行成功,返回值为非零,否则为零。
(5)CreatePopupMenu方法:该方法用于创建一个弹出式菜单窗口,并将其关联到菜单对象上。语法如下:
BOOL CreatePopupMenu( );
返回值:执行成功,返回值为非零,否则为零。对于弹出式菜单,如果菜单窗口被释放,菜单对象将要被自动释放。
(6)LoadMenu方法:该方法从应用程序的可执行文件中加载一个菜单资源,将其关联到菜单对象上。语法如下:
BOOL LoadMenu( LPCTSTR lpszResourceName );
BOOL LoadMenu( UINT nIDResource );
参数说明
返回值:执行成功,返回值为非零,否则为零。
(7)DestroyMenu方法:该方法用于释放菜单窗口,当菜单窗口被释放前,它将从菜单对象上分离出来。语法如下:
BOOL DestroyMenu( );
(8)DeleteMenu方法:该方法用于从菜单中删除一个菜单项。语法如下:
BOOL DeleteMenu( UINT nPosition, UINT nFlags );
参数说明
(9)TrackPopupMenu方法:该方法用于显示一个弹出式菜单。语法如下:
BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
参数说明
(10)AppendMenu方法:该方法用于在菜单项的末尾添加一个新菜单。语法如下:
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );
参数说明
(11)CheckMenuItem方法:该方法用于在弹出的菜单项中放置或删除标记。语法如下:
UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );
参数说明
(12)CheckMenuRadioItem方法:该方法用于将单选按钮放置在菜单项之前,或从组中所有的其他菜单项中删除单选按钮。语法如下:
BOOL CheckMenuRadioItem( UINT nIDFirst, UINT nIDLast, UINT nIDItem, UINT nFlags );
参数说明
(13)EnableMenuItem方法:该方法用于设置菜单项有效、无效或变灰。语法如下:
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
参数说明
(14)GetMenuItemCount方法:该方法用于返回弹出式菜单或顶层菜单的菜单数。语法如下:
UINT GetMenuItemCount( ) const;
返回值:如果菜单项没有子菜单,函数返回值为-1,否则返回子菜单数。
(15)GetMenuItemID方法:该方法根据菜单项的位置返回菜单ID,如果菜单项是一个弹出式菜单,返回值为-1;如果菜单项是一个分隔条,返回值为0。语法如下:
UINT GetMenuItemID( int nPos ) const;
参数说明
(16)GetMenuState方法:该方法用于返回指定菜单项的状态或弹出菜单的项数。语法如下:
UINT GetMenuState( UINT nID, UINT nFlags ) const;
参数说明
(17)GetMenuString方法:该方法用于设置菜单项的文本。语法如下:
int GetMenuString( UINT nIDItem, LPTSTR lpString, int nMaxCount, UINT nFlags ) const;
int GetMenuString( UINT nIDItem, CString& rString, UINT nFlags ) const;
参数说明
返回值:实际复制到缓冲区中的字符数。
(18)GetSubMenu方法:该方法用于获取弹出式菜单中的一个菜单项。语法如下:
CMenu* GetSubMenu( int nPos ) const;
参数说明
(19)InsertMenu方法:该方法用于向菜单中指定位置插入菜单项。语法如下:
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem
= NULL );
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );
参数说明
(20)ModifyMenu方法:该方法用于修改菜单项信息。语法如下:
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem
= NULL );
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );
参数说明
(21)RemoveMenu方法:该方法用于移除一个菜单项。语法如下:
BOOL RemoveMenu( UINT nPosition, UINT nFlags );
参数说明
(22)DrawItem方法:该方法是一个虚方法,用户可以改写该方法实现菜单的绘制。语法如下:
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
参数说明
6.1.2 菜单资源设计
下面来介绍如何设计菜单资源,步骤如下。
(1)在工作区窗口中选择资源视图(ResourceView),鼠标右键单击一个节点,在弹出的快捷菜单中选择“Insert”菜单项,将打开“Insert Resource”对话框,如图6.1所示。
图6.1 “Insert Resource”对话框
(2)在资源类型列表中选择“Menu”节点,单击“New”按钮,将创建一个菜单,如图6.2所示。
图6.2
(3)在菜单设计窗口中按<Enter>键打开“Menu Item Properties”窗口,在“Caption”编辑框中设计菜单标题,如图6.3所示。
图6.3 “Menu Item Properties”窗口
(4)关闭“Menu Item Properties”窗口,返回到菜单设计窗口中,如图6.4所示。
图6.4
(5)在新建菜单下的虚线框上按<Enter>键打开“Menu Item Properties”窗口可以添加子菜单,在“Menu Item Properties”窗口中设置子菜单ID和菜单的标题,如图6.5所示。
图6.5
如果用户不设置菜单ID,系统将会自动生成一个ID。
(6)关闭菜单属性窗口,返回到菜单设计窗口中,如图6.6所示。
(7)如果想要设计一个级联菜单,可以在菜单项的属性窗口中选中Pop-up复选框,这样,在菜单项的右侧将显示一个箭头,效果如图6.7所示。
(8)打开级联子菜单的属性窗口,设置菜单ID和菜单标题。重复以上的步骤就可以设置需要的菜单。
6.1.3 菜单的显示与命令处理
在编辑了菜单资源之后运行程序并不能显示菜单,要显示菜单,还需要将菜单和对话框资源进行关联,打开“Dialog Properties”窗口,在“General”选项卡的“Menu”列表框中选择要进行关联的菜单ID,如图6.8所示。
图6.8
现在运行程序就包含菜单了。但是想要用菜单进行对程序的操作还需要设置菜单的命令处理。打开类向导,选择“Message Maps”选项卡,在“Class name”列表框选择关联菜单的对话框类,在“Object Ids”列表中选择菜单项ID,在“Messages”列表中选择“COMMAND”项,如图6.9所示。
图6.9
单击“Add Function…”按钮,弹出“Add Member Function”对话框,并给出默认时的命令处理函数名,如图6.10所示。
图6.10 “Add Member Function”对话框
单击“OK”按钮,就添加了菜单项的命令处理函数。再单击“Edit Code”按钮可以打开添加的OnMenumessage命令处理函数,在这里可以编辑菜单项的功能,代码如下:
void CMenuDlg::OnMenumessage()
{
}
6.1.4 动态创建菜单
在前面已经介绍了如何编辑菜单资源以及通过类向导编写菜单的命令处理函数。本节将通过CMenu类实现动态创建菜单。
(1)创建一个基于对话框的应用程序。
(2)向对话框中添加一个按钮控件。
(3)在主窗口的头文件中声明一个CMenu类对象,代码如下:
CMenu m_Menu;
(4)在资源头文件(Resource.h)中定义命令ID,如图6.11所示。
图6.11
(5)在主窗口的头文件中添加消息处理函数,如图6.12所示。
图6.12
(6)在主窗口的源文件中添加消息映射宏,将命令ID关联到消息处理函数中,如图6.13所示。
(7)在主窗口的源文件中添加消息处理函数的实现代码,代码如下:
图6.13
void CDynamicMenuDlg::OnMenured()
{
}
void CDynamicMenuDlg::OnMenublue()
{
}
void CDynamicMenuDlg::OnMenugreen()
{
}
MessageBox函数用来弹出一个消息框,该函数有3个参数,第一个参数表示消息框显示的文本,第二个参数表示消息框的标题,默认为可执行文件名,第3个参数表示消息框显示的按钮风格和图标风格的组合。
(8)处理“创建”按钮的消息响应事件,用代码创建菜单,代码如下:
void CDynamicMenuDlg::OnButton1()
{
}
SetMenu函数用于分配一个菜单到指定窗口。
程序运行结果如图6.14所示。
图6.14
6.1.5 菜单项的更新机制
在使用类向导为菜单添加命令处理函数时,发现菜单除了COMMAND消息外,还有一个UPDATE_COMMAND_UI消息,该消息是“更新命令用户接口消息”。菜单项的状态维护就依赖于UPDATE_COMMAND_UI消息。下面就来看看如何使用这个消息。
打开一个基于单文档的应用程序,运行程序后发现“编辑”菜单下的菜单项都不可用,如图6.15所示。
如果想要使“编辑”菜单下的菜单项都可用,就要为相应的菜单项处理UPDATE_ COMMAND_UI消息。以“撤销”菜单项为例,打开类向导,选择“Message Maps”选项卡,在“Class name”列表框中选择“CmainFrame”类,在“Object Ids”列表中选择“撤销”菜单项“ID_EDIT_UNDO”,在“Messages”列表中选择“UPDATE_COMMAND_UI”项,单击“Add Function…”按钮,创建该消息的处理函数,如图6.16所示。
图6.16
单击“Edit Code”按钮,定位到新建的消息处理函数,在函数中添加代码使“撤销”菜单项可用,代码如下:
void CMainFrame::OnUpdateEditUndo(CCmdUI* pCmdUI)
{
}
程序运行结果如图6.17所示。
图6.17
通过上面的运行结果发现“撤销”菜单项已经可用,这是因为MFC菜单项的命令更新机制,当要显示菜单时,系统发出WM_INITMENUPOPUP消息,该消息被程序窗口的基类接管后会创建一个CCmdUI对象,CCmdUI对象会与程序的第一个菜单项相关联,并调用CCmdUI对象的DoUpdate方法发送UPDATE_COMMAND_UI消息,系统会查找是否有捕获这个消息的宏,如果找到就调用相应的函数进行处理。当这个菜单项更新后,CCmdUI对象会继续对下一个菜单项进行关联,直到完成所有菜单项的处理。
6.1.6 自绘弹出式菜单
在Windows操作系统中单击鼠标右键弹出的菜单称为弹出式菜单。弹出式菜单可以方便用户的操作,下面来介绍一下如何创建弹出式菜单。
(1)创建一个基于对话框的程序。
(2)向工程中导入两个位图,将资源ID改为IDB_LEFTBITMAP和IDB_ITEMBITMAP。
(3)以CMenu类为基类派生一个CCustomMenu类,定义一个记录菜单项信息的数据结构CMenuItemInfo,代码如下:
struct CMenuItemInfo
{
};
(4)在CCustomMenu类的头文件中声明如下变量:
CMenuItemInfo m_ItemLists[10]; //菜单项信息
int
CFont
(5)在CCustomMenu类中添加DrawMenuTitle方法,用于绘制菜单左侧标题,代码如下:
void CCustomMenu::DrawMenuTitle(CDC *pDC, CRect rect, CString title)
{
}
通过LoadBitmap方法装载位图IDB_LEFTBITMAP,再使用StretchBlt方法将位图绘制成标题大小。
(6)在CCustomMenu类中添加DrawItemText方法,用于绘制菜单项文本,代码如下:
void CCustomMenu::DrawItemText(CDC *pDC, LPSTR str, CRect rect)
{
}
(7)在CCustomMenu类中添加DrawSeparater方法,用于绘制分隔条,代码如下:
void CCustomMenu::DrawSeparater(CDC *pDC, CRect rect)
{
}
(8)在CCustomMenu类中添加ChangeMenuItem方法,用于修改菜单项信息,代码如下:
BOOL CCustomMenu::ChangeMenuItem(CMenu *menu)
{
}
(9)改写CMenu类的MeasureItem方法,设置菜单项大小,代码如下:
void CCustomMenu::MeasureItem(LPMEASUREITEMSTRUCT lpStruct)
{
}
(10)改写CMenu类的DrawItem方法,重绘菜单项,代码如下:
void CCustomMenu::DrawItem(LPDRAWITEMSTRUCT lpStruct)
{
}
(11)在主窗口的头文件中声明一个CCustomMenu类对象m_Menu,在主窗口的OnInit Dialog函数中添加如下代码,装载菜单资源。
m_Menu.LoadMenu(IDR_MENU1); //装载菜单资源
m_Menu.ChangeMenuItem(&m_Menu);
(12)处理主窗口的WM_CONTEXTMENU消息,弹出菜单,代码如下:
void CPopMenuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
}
当鼠标右键单击程序窗口的客户区时,程序会收到一条WM_CONTEXTMENU消息,在该消息的处理函数中使用TrackPopupMenu方法弹出菜单。
(13)在主窗口中添加消息WM_DRAWITEM和WM_MEASUREITEM,并分别在这两个消息的处理函数中调用m_Menu对象的DrawItem方法和MeasureItem方法。