菜单设计 CMenu

2023-11-11 11:18
文章标签 设计 菜单 cmenu

本文主要是介绍菜单设计 CMenu,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/389671

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

Windows如何添加右键新建菜单

Windows如何添加右键新建菜单 文章目录 Windows如何添加右键新建菜单实验环境缘起以新建`.md`文件为例第一步第二步第三步 总结 实验环境 Windows7 缘起 因为我习惯用 Markdown 格式写文本,每次新建一个.txt后都要手动修改为.md,真的麻烦。如何在右键新建菜单中添加.md选项呢? 网上有很多方法,这些方法我都尝试了,要么太麻烦,要么不凑效

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

开题报告中的研究方法设计:AI能帮你做什么?

AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 大家都准备开题报告了吗?研究方法部分是不是已经让你头疼到抓狂? 别急,这可是大多数人都会遇到的难题!尤其是研究方法设计这一块,选定性还是定量,怎么搞才能符合老师的要求? 每次到这儿,头脑一片空白。 好消息是,现在AI工具火得一塌糊涂,比如ChatGPT,居然能帮你在研究方法这块儿上出点主意。是不

创业者该如何设计公司的股权架构

本文来自七八点联合IT橘子和车库咖啡的一系列关于设计公司股权结构的讲座。 主讲人何德文: 在公司发展的不同阶段,创业者都会面临公司股权架构设计问题: 1.合伙人合伙创业第一天,就会面临股权架构设计问题(合伙人股权设计); 2.公司早期要引入天使资金,会面临股权架构设计问题(天使融资); 3.公司有三五十号人,要激励中层管理与重要技术人员和公司长期走下去,会面临股权架构设计问题(员工股权激