6.1  菜 单 设计

菜单是可视化编程的重要组成部分,是一种方便地给命令分组并访问这些命令的方法。菜单通常用来显示程序的各项功能,以方便用户选择执行,通过对菜单命令进行编程可以调用程序中相应的功能。

6.1.1  菜单类CMenu

在MFC中,CMenu类封装了Windows的菜单功能,它提供了多个方法用于创建、修改、合并菜单。CMenu类的主要方法如下。

(1)Attach方法:该方法用于将句柄关联到菜单对象上。语法如下:

BOOL Attach( HMENU hMenu );

参数说明

hMenu:标识菜单句柄。

返回值:为非零,表示执行成功,否则执行失败。

(2)Detach方法:该方法从菜单对象上分离菜单句柄。语法如下:

HMENU Detach( );

返回值:分离的菜单句柄。

(3)FromHandle方法:该方法根据菜单句柄返回一个菜单对象指针,如果句柄没有关联的菜单对象,则一个临时的菜单对象指针将要被创建。语法如下:

static CMenu* PASCAL FromHandle( HMENU hMenu );

参数说明

hMenu:标识菜单句柄。

返回值:菜单对象指针。

(4)CreateMenu方法:该方法用于创建一个菜单窗口,并将其关联到菜单对象上。语法如下:

BOOL CreateMenu( );

返回值:执行成功,返回值为非零,否则为零。

(5)CreatePopupMenu方法:该方法用于创建一个弹出式菜单窗口,并将其关联到菜单对象上。语法如下:

BOOL CreatePopupMenu( );

返回值:执行成功,返回值为非零,否则为零。对于弹出式菜单,如果菜单窗口被释放,菜单对象将要被自动释放。

(6)LoadMenu方法:该方法从应用程序的可执行文件中加载一个菜单资源,将其关联到菜单对象上。语法如下:

BOOL LoadMenu( LPCTSTR lpszResourceName );

BOOL LoadMenu( UINT nIDResource );

参数说明

lpszResourceName:标识资源名称。

nIDResource:标识资源ID。

返回值:执行成功,返回值为非零,否则为零。

(7)DestroyMenu方法:该方法用于释放菜单窗口,当菜单窗口被释放前,它将从菜单对象上分离出来。语法如下:

BOOL DestroyMenu( );

说明:当菜单对象的析构函数被调用时,将自动调用DestroyMenu方法释放菜单窗口。

(8)DeleteMenu方法:该方法用于从菜单中删除一个菜单项。语法如下:

BOOL DeleteMenu( UINT nPosition, UINT nFlags );

参数说明

nPosition:标识某一个菜单项。

nFlags:表示如何删除菜单项,可选值如下。

MF_BYCOMMAND:根据nPosition标识的菜单ID删除菜单项。

MF_BYPOSITION:根据nPosition标识的菜单位置删除菜单项。

(9)TrackPopupMenu方法:该方法用于显示一个弹出式菜单。语法如下:

BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );

参数说明

nFlags:表示屏幕位置标记和鼠标按钮标记。可选值如下。

TPM_CENTERALIGN:在x水平位置居中显示菜单。

TPM_LEFTALIGN:在x水平位置左方显示菜单。

TPM_RIGHTALIGN:在x水平位置右方显示菜单。

TPM_LEFTBUTTON:单击鼠标左键显示弹出式菜单。

TPM_RIGHTBUTTON :单击鼠标右键显示弹出式菜单。

x:以屏幕坐标标识弹出式菜单的水平坐标。

y:以屏幕坐标标识弹出式菜单的垂直坐标。

pWnd:标识弹出式菜单的所有者。

lpRect:以屏幕坐标表示用户在菜单中的单击区域,如果为NULL,当用户单击弹出式菜单之外的区域,将释放菜单窗口。

(10)AppendMenu方法:该方法用于在菜单项的末尾添加一个新菜单。语法如下:

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

参数说明

nFlags:标识菜单项的状态信息。

nIDNewItem:标识菜单项的ID。

lpszNewItem:标识菜单项的内容。

pBmp:标识关联菜单项的位图对象指针。

(11)CheckMenuItem方法:该方法用于在弹出的菜单项中放置或删除标记。语法如下:

UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );

参数说明

nIDCheckItem:指定由nCheck确定的将要检测的菜单项。

nCheck:指定如何检测菜单项,并如何决定菜单中子菜单的位置。可选值如下。

MF_BYCOMMAND:指定参数给出已存在菜单项的命令ID号,为默认值。

MF_BYPOSITION:指定参数给出已存在菜单项的位置,第一项位置为0。

MF_CHECKED:与MF_UNCHECKED一起用作开关,在菜单项之前放置默认的检测标记。

MF_UNCHECKED:与MF_CHECKED一起用作开关,删除菜单项之前的检测标记。

(12)CheckMenuRadioItem方法:该方法用于将单选按钮放置在菜单项之前,或从组中所有的其他菜单项中删除单选按钮。语法如下:

BOOL CheckMenuRadioItem( UINT nIDFirst, UINT nIDLast, UINT nIDItem, UINT nFlags );

参数说明

nIDFirst:指定单选按钮组中的第一个菜单项的值。

nIDLast:指定单选按钮组中的最后一个菜单项的值。

nIDItem:指定单选按钮组中被选中的菜单项的值。

nFlags:对nIDFirst、nIDLast和nIDItem的解释,可选值如下:

MF_BYCOMMAND:指定参数给出已存在菜单项的命令ID号,若没有设置MF_ BYCOMMAND和MF_BYPOSITION,该值为默认值。

MF_BYPOSITION:指定参数给出已存在菜单项的位置,第一项位置为0。

(13)EnableMenuItem方法:该方法用于设置菜单项有效、无效或变灰。语法如下:

UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );

参数说明

nIDEnableItem:指定nEnable决定的将要有效的菜单项,该参数既可以指定弹出式菜单,也可以指定标准菜单项。

nEnable:指定了将要进行的动作,可选值如下。

MF_BYCOMMAND:指定参数给出已存在的菜单项的命令ID号,此值为默认值。

MF_BYPOSITION:指定参数给出已存在菜单项的位置,第一项位置为0。

MF_DISABLED:使菜单项无效,但不变灰。

MF_ENABLED:使菜单项有效。

MF_GRAYED:使菜单项变灰。

(14)GetMenuItemCount方法:该方法用于返回弹出式菜单或顶层菜单的菜单数。语法如下:

UINT GetMenuItemCount( ) const;

返回值:如果菜单项没有子菜单,函数返回值为-1,否则返回子菜单数。

(15)GetMenuItemID方法:该方法根据菜单项的位置返回菜单ID,如果菜单项是一个弹出式菜单,返回值为-1;如果菜单项是一个分隔条,返回值为0。语法如下:

UINT GetMenuItemID( int nPos ) const;

参数说明

nPos:标识菜单项的位置。

(16)GetMenuState方法:该方法用于返回指定菜单项的状态或弹出菜单的项数。语法如下:

UINT GetMenuState( UINT nID, UINT nFlags ) const;

参数说明

nID:指定由nFlags决定的菜单项ID。

nFlags:指定nID的特性,值为MF_BYCOMMAND和MF_BYPOSITION之一。

(17)GetMenuString方法:该方法用于设置菜单项的文本。语法如下:

int GetMenuString( UINT nIDItem, LPTSTR lpString, int nMaxCount, UINT nFlags ) const;

int GetMenuString( UINT nIDItem, CString& rString, UINT nFlags ) const;

参数说明

nIDItem:标识菜单项位置或菜单项命令ID,具体含义依赖于nFlags参数。

lpString:标识一个字符缓冲区。

nMaxCount:标识向字符缓冲区中复制的最大字符数。

rString:标识一个字符串。

nFlags:表示如何解释nIDItem。如果为MF_BYCOMMAND,nIDItem标识菜单项命令ID;如果为MF_BYPOSITION,nIDItem标识菜单项位置。

返回值:实际复制到缓冲区中的字符数。

(18)GetSubMenu方法:该方法用于获取弹出式菜单中的一个菜单项。语法如下:

CMenu* GetSubMenu( int nPos ) const;

参数说明

nPos:标识菜单项位置,第一个菜单项对应的位置是0,第二个菜单项对应的位置是1,依次类推。

(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 );

参数说明

nPosition:标识某一个菜单项。

nFlags:表示如何解释nPosition,可选值如下:

MF_BYCOMMAND:根据nPosition标识的菜单ID插入菜单项。

MF_BYPOSITION:根据nPosition标识的菜单位置插入菜单项。

nIDNewItem:标识菜单项的ID。

lpszNewItem:标识菜单项的内容。

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 );

参数说明

nPosition:标识某一个菜单项。

nFlags:表示如何解释nPosition,可选值如下。

MF_BYCOMMAND:根据nPosition标识的菜单ID修改菜单项。

MF_BYPOSITION:根据nPosition标识的菜单位置修改菜单项。

nIDNewItem:标识菜单项的ID。

lpszNewItem:标识菜单项的内容。

pBmp:标识关联菜单项的位图对象指针。

(21)RemoveMenu方法:该方法用于移除一个菜单项。语法如下:

BOOL RemoveMenu( UINT nPosition, UINT nFlags );

参数说明

nPosition:标识某一个菜单项。

nFlags:表示如何删除菜单项,可选值如下:

MF_BYCOMMAND:根据nPosition标识的菜单ID删除菜单项。

MF_BYPOSITION:根据nPosition标识的菜单位置删除菜单项。

(22)DrawItem方法:该方法是一个虚方法,用户可以改写该方法实现菜单的绘制。语法如下:

virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );

参数说明

lpDrawItemStruct:是一个DRAWITEMSTRUCT结构指针,该结构包含了菜单项的ID、类型、画布、句柄等详细信息。

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  “Menu Item Properties”窗口

如果用户不设置菜单ID,系统将会自动生成一个ID。

(6)关闭菜单属性窗口,返回到菜单设计窗口中,如图6.6所示。

说明:如果要插入一个分隔线,可以选择Separator属性,并且菜单资源设计窗口支持鼠标拖曳功能,可以在选中菜单项后将其拖曳至适当位置,从而调整各个菜单的位置。

(7)如果想要设计一个级联菜单,可以在菜单项的属性窗口中选中Pop-up复选框,这样,在菜单项的右侧将显示一个箭头,效果如图6.7所示。

图6.6  菜单设计窗口                              图6.7  菜单设计窗口

说明:如果为菜单项选中Pop-up属性,则该菜单项的ID将不能编辑。

(8)打开级联子菜单的属性窗口,设置菜单ID和菜单标题。重复以上的步骤就可以设置需要的菜单。

说明:在设计菜单项信息时,可以为菜单项设置加速键来简化用户操作。在菜单标题的后面加“&+字母”即可实现加速键的设置,程序运行时,用户按下〈Alt〉键加上该字母键,便可激活并操作该菜单。如果设置加速键的菜单项是子菜单,还需要为该菜单的上级菜单设置加速键,否则上级菜单没有加速键就不能运行子菜单的加速键。

6.1.3  菜单的显示与命令处理

在编辑了菜单资源之后运行程序并不能显示菜单,要显示菜单,还需要将菜单和对话框资源进行关联,打开“Dialog Properties”窗口,在“General”选项卡的“Menu”列表框中选择要进行关联的菜单ID,如图6.8所示。

图6.8  “Dialog Properties”窗口

现在运行程序就包含菜单了。但是想要用菜单进行对程序的操作还需要设置菜单的命令处理。打开类向导,选择“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()

{

MessageBox("菜单消息处理函数");

return;

}

6.1.4  动态创建菜单

在前面已经介绍了如何编辑菜单资源以及通过类向导编写菜单的命令处理函数。本节将通过CMenu类实现动态创建菜单。

(1)创建一个基于对话框的应用程序。

(2)向对话框中添加一个按钮控件。

(3)在主窗口的头文件中声明一个CMenu类对象,代码如下:

CMenu m_Menu;

(4)在资源头文件(Resource.h)中定义命令ID,如图6.11所示。

图6.11  Resource.h文件

(5)在主窗口的头文件中添加消息处理函数,如图6.12所示。

图6.12  DynamicMenuDlg.h文件

(6)在主窗口的源文件中添加消息映射宏,将命令ID关联到消息处理函数中,如图6.13所示。

(7)在主窗口的源文件中添加消息处理函数的实现代码,代码如下:

图6.13  DynamicMenuDlg.cpp文件

void CDynamicMenuDlg::OnMenured()

{

MessageBox("打开菜单被按下");

}

void CDynamicMenuDlg::OnMenublue()

{

MessageBox("保存菜单被按下");

}

void CDynamicMenuDlg::OnMenugreen()

{

MessageBox("另存为菜单被按下");

}

MessageBox函数用来弹出一个消息框,该函数有3个参数,第一个参数表示消息框显示的文本,第二个参数表示消息框的标题,默认为可执行文件名,第3个参数表示消息框显示的按钮风格和图标风格的组合。

(8)处理“创建”按钮的消息响应事件,用代码创建菜单,代码如下:

void CDynamicMenuDlg::OnButton1()

{

m_Menu.CreateMenu();

CMenu popmenu;

popmenu.CreatePopupMenu();

m_Menu.AppendMenu(MF_POPUP,(UINT)popmenu.m_hMenu,"系统");

popmenu.AppendMenu(MF_STRING,ID_MENURED,"打开");

popmenu.AppendMenu(MF_STRING,ID_MENUBLUE,"保存");

popmenu.AppendMenu(MF_STRING,ID_MENUGREEN,"另存为");

popmenu.Detach();

SetMenu(&m_Menu);

}

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)

{

pCmdUI->Enable(); //使菜单项可用

}

说明:参数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

{

CString m_ItemText;  //菜单项文本

int     m_ItemIndex; //菜单项索引

int     m_ItemID;     //菜单标记0分隔条,其他普通菜单

};

(4)在CCustomMenu类的头文件中声明如下变量:

CMenuItemInfo m_ItemLists[10]; //菜单项信息

int                    m_ImageIndex;  //图像索引

CFont                 m_TitleFont;   //标题字体

(5)在CCustomMenu类中添加DrawMenuTitle方法,用于绘制菜单左侧标题,代码如下:

void CCustomMenu::DrawMenuTitle(CDC *pDC, CRect rect, CString title)

{

CBitmap m_bitmap;

m_bitmap.LoadBitmap(IDB_LEFTBITMAP);

BITMAP m_size;

m_bitmap.GetBitmap(&m_size);

CDC m_memdc;

m_memdc.CreateCompatibleDC(pDC);

CGdiObject* m_oldobject;

m_oldobject = m_memdc.SelectObject(&m_bitmap);

pDC->StretchBlt(0,0,28,rect.bottom,&m_memdc,0,0,m_size.bmWidth,m_size.bmHeight,SRCCOPY);

m_bitmap.DeleteObject();

CFont* m_oldfont = pDC->SelectObject(&m_TitleFont);

pDC->TextOut(rect.left+5,rect.Height()+130,title);

pDC->SelectObject(m_oldfont);

}

通过LoadBitmap方法装载位图IDB_LEFTBITMAP,再使用StretchBlt方法将位图绘制成标题大小。

(6)在CCustomMenu类中添加DrawItemText方法,用于绘制菜单项文本,代码如下:

void CCustomMenu::DrawItemText(CDC *pDC, LPSTR str, CRect rect)

{

rect.DeflateRect(40,0,0,0);

pDC->DrawText(str,rect,DT_SINGLELINE|DT_VCENTER|DT_LEFT);

}

(7)在CCustomMenu类中添加DrawSeparater方法,用于绘制分隔条,代码如下:

void CCustomMenu::DrawSeparater(CDC *pDC, CRect rect)

{

if(pDC != NULL)

{

rect.DeflateRect(29,0,0,0);

pDC->Draw3dRect(rect,RGB(0,0,255),RGB(0,0,255));

}

}

(8)在CCustomMenu类中添加ChangeMenuItem方法,用于修改菜单项信息,代码如下:

BOOL CCustomMenu::ChangeMenuItem(CMenu *menu)

{

int m_index=0;

if(menu != NULL)

{

int m_itemcount = menu->GetMenuItemCount(); //获得菜单项个数

for(int i=0;i<m_itemcount;i++)

{

//获得菜单项文本

menu->GetMenuString(i,m_ItemLists[m_index].m_ItemText,MF_BYPOSITION);

int m_itemID = menu->GetMenuItemID(i); //获得菜单项ID

m_ItemLists[m_index].m_ItemID = m_itemID;

if(m_itemID>0)

{

m_ItemLists[m_index].m_ItemIndex= m_ImageIndex;

m_ImageIndex++;

}

//修改菜单项信息

menu->ModifyMenu(i,MF_OWNERDRAW | MF_BYPOSITION | MF_STRING,

m_ItemLists[m_index].m_ItemID,(LPSTR)&(m_ItemLists[m_index]));

m_index++;

CMenu* m_subMenu = menu->GetSubMenu(i);

if(m_subMenu)

{

ChangeMenuItem(m_subMenu);

}

}

}

return TRUE;

}

(9)改写CMenu类的MeasureItem方法,设置菜单项大小,代码如下:

void CCustomMenu::MeasureItem(LPMEASUREITEMSTRUCT lpStruct)

{

if(lpStruct->CtlType= =ODT_MENU)

{

lpStruct->itemHeight = 22; //设置菜单项的高

lpStruct->itemWidth = 100; //设置菜单项的宽

CMenuItemInfo* m_iteminfo;

m_iteminfo = (CMenuItemInfo*)lpStruct->itemData;

if(m_iteminfo->m_ItemID = = 0)

{

lpStruct->itemHeight = 1; //设置分隔条

}

}

}

(10)改写CMenu类的DrawItem方法,重绘菜单项,代码如下:

void CCustomMenu::DrawItem(LPDRAWITEMSTRUCT lpStruct)

{

if(lpStruct->CtlType= =ODT_MENU)

{

if(lpStruct->itemData = = NULL)    return;

unsigned int m_state = lpStruct->itemState; //获得菜单项状态

CDC* pDC = CDC::FromHandle(lpStruct->hDC);

CString str = ((CMenuItemInfo*)(lpStruct->itemData))->m_ItemText;

LPSTR m_str = str.GetBuffer(str.GetLength());

int m_itemID = ((CMenuItemInfo*)(lpStruct->itemData))->m_ItemID;

int m_itemicon = ((CMenuItemInfo*)(lpStruct->itemData))->m_ItemIndex;

CRect m_rect = lpStruct->rcItem;

pDC->SetBkMode(TRANSPARENT); //设置菜单项文本背景透明

switch(m_itemID)

{

case 0:     //绘制分隔条

DrawSeparater(pDC,m_rect);

break;

default:   //绘制菜单项和左侧标题

DrawComMenu(pDC,m_rect,m_state&ODS_SELECTED);

DrawItemText(pDC,m_str,m_rect);

DrawMenuTitle(pDC,m_rect,"明日科技有限公司");

break;

}

}

}

(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)

{

CMenu* m_tempmenu = m_Menu.GetSubMenu(0);

m_tempmenu->TrackPopupMenu(TPM_LEFTBUTTON|TPM_LEFTALIGN,point.x,point.y,this);

}

当鼠标右键单击程序窗口的客户区时,程序会收到一条WM_CONTEXTMENU消息,在该消息的处理函数中使用TrackPopupMenu方法弹出菜单。

(13)在主窗口中添加消息WM_DRAWITEM和WM_MEASUREITEM,并分别在这两个消息的处理函数中调用m_Menu对象的DrawItem方法和MeasureItem方法。

菜单栏

在对话框窗口里显示菜单栏

像工具栏一样,菜单栏在按件面板里没有对应的选项,但有一个菜单控件类CMenu,所以如果想要在对话框里显示菜单栏,就得像工具栏那样,到ResourceView选项卡里新建一个菜单栏资源,步骤跟新建工具栏资源一样,只是资源类型是:Menu,菜单资源设计如下图:

如果想改菜单项文本内容的话,方法是右击要更改的菜单项,选择属性,接着会弹出这样一个对话框:

上面那个ID项就是该菜单项对应的ID号了,添加菜单项单击消息处理函数时会用到,而标明项里的内容就是菜单项要显示的文本了。

这里还得注意一下“弹出”这个选项,勾上这个选项表明对应的菜单项还有下级菜单,如:

上面“转到”这个菜单项具有弹出属性,有下级菜单

设计好了菜单资源,接着我们就来在对话框显示菜单栏吧,方法是进入对话框编辑区,右击对话框界面,选择属性,然后在菜单项里选择菜单资源ID号,回车,编译,运行,效果如下图:

当然还有第二种在对话框显示菜单的方法:调用SetMenu函数把菜单跟对话框关联起来,函数第一个参数是窗口句柄,第二个参数是菜单句柄。在OnInitDialog函数里添加如下语句:
 CMenu menu;//定义一个菜单类变量
 menu.LoadMenu(IDR_MENU1);//装载IDR_MENU1菜单资源
 SetMenu(&menu);//和当前窗口关联起来
 menu.Detach();//分离

如果要处理菜单项单击消息的话,方法跟处理工具栏项单击消息一样,进入类向导,找到对应的菜单项ID,为它添加COMMAND消息处理函数。
 设置菜单左边显示位图和背景位图

CMenu类里要了解的函数

SetMenuItemBitmaps//设置菜单项左边的位图

函数定义:BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );
nPostion指明具体要设置的菜单项,可以是菜单项索引,菜单项ID,具体由nFlags参数指明,为MF_BYPOSITION,则以菜单项索引指明,

为MF_BYCOMMAND则第一个参数nPosition是菜单项ID号。pBmpUnchecked未被检测时显示的位图(正常时),pBmpChecked检测时显示的图片(就是菜单项被打上勾时所显示的图片,跟CheckMenuItem函数有关联)

一个API函数SetMenuInfo,这个函数可以设置菜单重绘时选择的填充画刷类型,该函数有两个参数,第一个是要设置的菜单句柄,第二个是一个MENUINFO结构指针,我们只要了解这结构里有一个成员hbrBack就行了,hbrBack是一个画刷句柄,而菜单背景位图就通过创建位图画刷来实现的。

好了,以上面的工程为例,引入三张位图,ID号默认不变,然后再引入一张位图(菜单背景位图,ID:IDB_MENUBACK),接着在对话框类的OnInitDialog函数里添加如下语句:

CMenu *pMenu=GetMenu();//获取对话框关联菜单
 CMenu *pSubMenu=pMenu->GetSubMenu(0);//获得子菜单(如果有)0表示索引,对应“文件”菜单
 for(int i=0;i<3;i++)
 {
     CBitmap bmp;
     bmp.LoadBitmap(IDB_BITMAP1+i);
  pSubMenu->SetMenuItemBitmaps(i,MF_BYPOSITION,&bmp,&bmp);
  bmp.Detach();
 }
 CBitmap bmp;
 CBrush m_BKBrush;
 bmp.LoadBitmap(IDB_MENUBACK);
 m_BKBrush.CreatePatternBrush(&bmp);//创建位图画刷
 MENUINFO mnInfo;
 memset(&mnInfo,0,sizeof(MENUINFO));
 mnInfo.cbSize=sizeof(MENUINFO);
 mnInfo.fMask=MIM_BACKGROUND;
 mnInfo.hbrBack=m_BKBrush;
 ::SetMenuInfo(pSubMenu->m_hMenu,&mnInfo);
    m_BKBrush.Detach();

运行效果如下图:

(PS:不知道大家有没有碰到过这个问题,MENUINFO结构未定义,解决的方法是进入文件选项卡(FileView),在Source  File文件下的StdAfx.cpp文件里的最前面部分添加这个语句:#define  WINVER 0x0501)

设计弹出式菜单

CMenu类里要了解的函数:

TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd,LPCRECT lpRect = NULL );
该函数用于在界面显示菜单,nFlags参数指明菜单显示标志,x,y用于确定菜单显示基于的坐标点,pWnd是菜单相关联的窗口。

在“MFC类库详解”中有关参数nFlags的解释如下:

  指定屏幕位置标志或鼠标键标志。
屏幕位置标志可以为下列值之一:

· TPM_CENTERALIGN 使弹出菜单在水平方向相对于x指定的坐标居中。
· TPM_LEFTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置左对齐。
· TPM_RIGHTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置右对齐。

鼠标键标志可以为下列值之一:

· TPM_LEFTBUTTON 导致弹出菜单追踪鼠标左键。
· TPM_RIGHTBUTTON 导致弹出菜单追踪鼠标右键。

以上面工程为例,给对话框添加鼠标右键抬起(WM_RBUTTONUP)消息处理函数,在函数里添加如下代码:
void CSeventhDlg::OnRButtonUp(UINT nFlags, CPoint point)
{
 // TODO: Add your message handler code here and/or call default
 CMenu *Menu=GetMenu();
 ClientToScreen(&point);//将窗口坐标转换成屏幕坐标
 Menu->GetSubMenu(0)->TrackPopupMenu(
  TPM_LEFTBUTTON|TPM_VERTICAL,point.x,point.y,this);
 Menu->Detach();
 CDialog::OnRButtonUp(nFlags, point);
}
要注意的是,要在界面显示的菜单,必须是一个弹出菜单,虽然Menu->TrackPopupMenu也可以显示,但显然不是想要的结果。

运行效果:

动态(纯代码)创建一个菜单
上面的例子,都是使用了菜单资源创建的菜单,这一次我们用代码来创建菜单,其实本质跟前面的用控件类的Create函数创建一个控件一样。只不过这里的“Create”函数是CreateMenu和CreatePopupMenu函数。

CMenu类里要了解的函数:

CreateMenu //创建一个主菜单,函数没有参数

CreatePopupMenu//创建一个具有弹出属性的菜单,函数没有参数

AppendMenu//往一个菜单里添加菜单项,或弹出式菜单

AppendMenu函数定义如下:

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );
nFlags参数常用取值如下:

MF_STRING 表明添加的是一个不具有弹出属性的菜单项。

MF_POPUP 添加的一个弹出菜单项

MF_SEPARATOR 添加的是一个菜单分隔条

MF_OWNERDRAW  表明对应菜单具有自绘属性

nIDNewItem参数,如果添加的是一个不具有弹出属性的菜单项,那么该值就是菜单项ID号,否则是弹出式菜单句柄,lpszNewItem是菜单项名称(菜单文本内容)

好了,接着我们来动态创建一个菜单,首先往对话框里添加一个按钮控件,当单击这个按钮时,就创建菜单,响应这个按钮控件的单击消息,消息处理函数里添加如下代码:

CMenu Menu;
  Menu.CreateMenu();//创建一个主菜单
  CMenu popMenu;
  popMenu.CreatePopupMenu();//创建一个弹出式菜单
  popMenu.AppendMenu(MF_STRING,1001,"新建");//添加菜单项
  popMenu.AppendMenu(MF_STRING,1002,"打开");
  popMenu.AppendMenu(MF_STRING,1003,"保存");
  Menu.AppendMenu(MF_POPUP,(UINT)popMenu.m_hMenu,"文件");//添加弹出菜单项
  SetMenu(&Menu);//关联到窗口中
  Menu.Detach();
  popMenu.Detach();

更改菜单项大小(宽高),设置菜单文本字体大小

由于CMenu类里并没有提供设置菜单项大小以及字体大小的函数,所以我们如果要实现上述功能的话,只能采取自绘的方法。

如果对CMenu类的某些函数不了解的话,可以参考"MFC 类大全",这里就不讲述了

首先从CMenu派生出一个子类CNewMenu(类的类型为Generic Class),然后往这个类添加三个成员函数,MeasureItem(设置菜单宽高),

DrawItem(自绘菜单),ChangeMenuItem(修改菜单项类型)

三个函数分别定义如下:

void CNewMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)

void CNewMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

void CNewMenu::ChangeMenuItem(CMenu *pMenu)

其中MeasureItem和DrawItem是CMenu类的虚函数。

各函数的代码如下:

void CNewMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
 lpMeasureItemStruct->itemHeight=25;//项高
 lpMeasureItemStruct->itemWidth=120;//项宽
}

void CNewMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
 CRect rect=lpDrawItemStruct->rcItem;
 CDC dc;
 dc.Attach(lpDrawItemStruct->hDC);
 dc.FillSolidRect(rect,RGB(0,166,170));
 CFont Font;
 Font.CreatePointFont(125,"宋体");//创建字体
 dc.SelectObject(&Font);
 CString *pText=(CString *)lpDrawItemStruct->itemData;
 if(lpDrawItemStruct->itemState&ODS_SELECTED)
   dc.FillSolidRect(rect,RGB(80,89,202));//菜单被选中
 dc.SetTextColor(RGB(10,0,181));//设置文本颜色
 dc.DrawText(*pText,rect,DT_VCENTER|DT_LEFT|DT_SINGLELINE);
 dc.Detach();

}

void CNewMenu::ChangeMenuItem(CMenu *pMenu)
{
int itemCount=pMenu->GetMenuItemCount();
for(int i=0;i<itemCount;i++)
{
    CString *pText=new CString;
 UINT itemID=pMenu->GetMenuItemID(i);//获取菜单项ID号
 pMenu->GetMenuString(i,*pText,MF_BYPOSITION);//获取菜单文本

//ModifyMenu函数最后一个参数对应DRAWITEMSTRUCT结构里的itemData变量
 pMenu->ModifyMenu(i,MF_OWNERDRAW|MF_BYPOSITION|MF_STRING,itemID,(LPSTR)pText);
  if(itemID==-1)//如果是一个弹出式菜单
 {
  ChangeMenuItem(pMenu->GetSubMenu(i));
 }
}  
}
必须让每个菜单项具有MF_OWNERDRAW属性,不然接不到WM_MEASUREITEM和WM_DRAWITEM消息,而且菜单项不具有MF_OWNERDRAW属性, 即使接到消息,也无法自绘,所以上面的ChangeMenuItem函数就是用于修改菜单项属性
WM_MEASUREITEM和WM_DRAWITEM消息不是直接发给菜单窗口的,会被父窗口给收到,所以得处理父窗口的WM_MEASUREITEM和WM_DRAWITEM消息,给话框类添加这两个消息处理函数,两个函数里的代码分别如下:

void CFirstDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
 // TODO: Add your message handler code here and/or call default
     if(lpMeasureItemStruct->CtlType==ODT_MENU)//如果类型是菜单
       newMenu.MeasureItem(lpMeasureItemStruct);//调用CNewMenu类的MeasureItem成员函数
       else
    CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
  
}

void CFirstDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
 // TODO: Add your message handler code here and/or call default
 if(lpDrawItemStruct->CtlType==ODT_MENU)
  newMenu.DrawItem(lpDrawItemStruct);
 else
 CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

接着我们在对话类添加一个成员变量:

CNewMenu newMenu; (记得要包含头文件:"NewMenu.h"),然后在对话框类的OnInitDialog函数添加如下代码:

newMenu.LoadMenu(IDR_MENU1);
 SetMenu(&newMenu);
 //只更改下主菜单下的第一项,更改全部:newMenu.ChangeMenuItem(&newMenu);
 newMenu.ChangeMenuItem(newMenu.GetSubMenu(0));

IDR_MENU1是菜单资源的ID号,可自行创建。好了,到了这里大功已经告成了,点编译运行,效果如下:

跟自绘按钮控件一样,上面依然没处理菜单的所有状态,如获得焦点,被核记,有无关联图片。也不处理菜单分隔条。。
如果想处理这些状态的话。建议查看DRAWITEMSTRUCT结构的itemState变量,这个成员指明菜单项的状态,
关联图片,就查看CMenu类的函数。。。

(菜单项数据(itemData)对应的分配内存,就自己释放吧)

菜单设计 CMenu相关推荐

  1. 业务系统设计之一:系统菜单设计

    本文主要介绍业务系统设计里的菜单设计,关注我博客的朋友或许以前见过一篇我写的关于菜单的文章,文章连接:使用RadControls的RadMenu控件开发系统菜单 ,这篇文章主要是介绍了RadContr ...

  2. 70+漂亮且极具亲和力的导航菜单设计推荐

    网站可用性是任何网站的基本要素,而可用的导航更是网站所必需的要素之一.导航决定了用户如何与网站进行交互.如果没有了可用的导航,那么网站内容就会变得毫无用处.导航菜单应当足够简单以让用户快速了解它,但还 ...

  3. 【设计】精致的侧滑菜单设计案例

    侧滑菜单已经成为app一个极常用的设计,不管是事务类,效率类还是生活类app.侧滑菜单因Path 2.0和Facebook为开发者熟知,本文搜集了一些时髦的,精致的侧滑菜单设计案例. 优雅的纯色背景, ...

  4. .NET CORE(C#) WPF 值得推荐的动画菜单设计

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. .NET CORE(C#) WPF 值得推荐的动画菜单设计 阅读导航 本文背景 代码实现 本文 ...

  5. 2015年主流的页面导航菜单设计

    在移动版应用网站中,用汉堡菜单图标已经成为主流的一种很常见的导航设计模式了,由于它不会干扰用户使用的任务过程,只要在需要的时候点击图标来访问菜单,很多可以让人眼花缭乱的元素都可以被隐藏在汉堡包菜单中, ...

  6. JQuery——横纵向菜单设计

    JQuery就是用JS写的,方便编程的一个方法集合.jQuery就好比把一些js的效果事先做好了,并进行了封装,然后直接调用就行了.你用javascript实现一个动画效果说不定写很长的一段代码,然而 ...

  7. 70 漂亮且极具亲和力的导航菜单设计推荐

    网站可用性是任何网站的基本要素,而可用的导航更是网站所必需的要素之一.导航决定了用户如何与网站进行交互.如果没有了可用的导航,那么网站内容就会变得毫无用处.导航菜单应当足够简单以让用户快速了解它,但还 ...

  8. android第三方菜单设计,Android菜单设计指南(上)

    菜单设计快速预览 •    任何指令的选项菜单只针对当前的操作. •    任何指令的(弹出式)文本菜单只针对当前被选中项. •    优先排序频繁使用的指令(or功能). •    将最重要的指令( ...

  9. html导航栏怎么加点,点靓网页的10种导航菜单设计

    网页导航菜单设计关系到你的网站整体设计成败,所以不少优秀的网站设计通过颜色.排版.形状和一些图片的精心修饰来帮助网站创造更好的视觉效果,我们从设计师们的收集中精选了这些从极简到拥有众多细节的不同设计, ...

最新文章

  1. C++ 函数的模板的使用
  2. java交通工具的类继承代码_Java作业-交通工具继承
  3. qt android 开发之wifi开发篇
  4. ZOJ 3609 Modular Inverse(扩展欧几里得)题解
  5. spring第五讲:aop
  6. continue语句只用于循环语句中,它的作用是什么?
  7. 【乘风破浪的开发者】丁一超:从AI实战营出发探索未知的AI世界
  8. 优秀分层UI设计模板解析提升网站用户体验,5个UI设计技巧轻松搞定
  9. 3.2生产者和消费者(Producers and Consumers)
  10. MySQL安装及应用合集(4):MySQL库表基本操作-增删改查
  11. PPT到WORD的组合图片复制
  12. Flink DataStream Keyed和Non-Keyed窗口、WindowAssigner、窗口生命周期、窗口函数、迟到数据和窗口结果的处理
  13. 神经网络中的filter (滤波器)与kernel(内核)的概念
  14. 解决LaTeX中的\pdfendlink ended up in different nesting level than \pdfstartlink.问题
  15. 微信二次开发第一弹 —— 与微信握手
  16. Android mc怎么和win10联机,我的世界实现跨平台联机 Win10玩家可与手机互联
  17. 大风车吱呀吱悠悠地转
  18. python爬取当当图片和信息
  19. SSM(spring.struts2.mybatis)注解式开发步骤
  20. Echarts折线图的平移假动画

热门文章

  1. 代码重复率PHP,终端代码重复率检测实践
  2. 维纳滤波器推导以及MATLAB代码(Wiener Filter)
  3. 高频率Vue面试题总结,vue面试就靠它!
  4. 【英语面试】二.计算机专业英语面试常见问题(研究方向/前沿技术/本科毕设篇)
  5. U盘出现RAW问题时的数据恢复
  6. CodeGear RAD 2007 up4最新下载及破解
  7. unity3d七彩珠游戏
  8. 速读《构建之法 现代软件工程》的五个问题
  9. 火车头采集器向dedecms发布文章,使阅读权限为待审核稿件
  10. 安全测试——验证日志文件