VC++:菜单的使用总结

article/2025/10/26 17:57:33

 

菜单是windows应用程序经常使用的资源之一。从今天开始,把有关菜单的创建及使用等各方面的基本用法总结到一起,算是做个笔记吧!准备一天写一点,陆陆续续完成!

一、菜单的创建:

(一)VC++菜单通过编辑资源文件直接创建菜单

可以自己编写菜单资源文件来完成来手工创建菜单,这与编程方法创建菜单不同。用这种方法创建菜单分两步:

1、在rc资源文件中编写菜单资源:

首先,要用其它文字编辑软件打开rc文件,然后在里面手工编写菜单资源。举例如下:

IDR_MENU1 MENU PRELOAD DISCARDABLE     

(注释:"IDR_MENU1"-->菜单ID名; "MENU"-->资源类名)

BEGIN   

(注释:以BEGIN开始,END结束菜单定义)

           POPUP "&File"  

(注释:POPUP--> 表明是一个弹出菜单 ;“&File"-->为菜单名字。"&"定义紧接后面的字母F快捷键。)

           BEGIN

                     MENUITEM "&New" ID_FILE_NEW

                    (注释:MENUITEM-->子菜单项;ID_FILE_NEW是ID值。)

                     MENUITEM "&Edit" ID_FILE_EDIT

                     MENUITEM SEPARATOR    

                   (注释:SEPARATOR-->分隔水平线;后面不需要跟ID值。)

                      MENUITEM "&Copy" ID_FILE_COPY

                      MENUITEM "E&xit" ID_FILE_EXIT

           END

END

其次,编辑resource.h文件,对菜单中的各种ID进行定义。

#define ID_FILE_NEW 0X8001

#define ID_FILE_EDIT 0X8002

#define ID_FILE_COPY 0X8003

#define ID_FILE_EXIT 0X8004

通过以上两步,就已经成功编辑了一个名为”File",ID为IDR_MENU1的菜单,此菜单弹出时,有“New","Edit","Copy","Exit"等四项菜单命令组成的子菜单。

定义一个CMenu变量,通过CMenu.LoadMenu()方法可以加载上面手工编成的菜单:

CMenu menu;

menu.loadMenu(IDR_MENU1);

然后通过menu变量就可以进一步操作菜单了。

也可以通过CFrameWnd::LoadFrame 加载菜单,直接把菜单挂载到框架窗口上面。如:

((CMainFrame *)pMainWnd)->LoadFrame(IDR_MENU1);

有些菜单是系统编制的,如程序的“系统菜单”。这种菜单你只可以在系统的基础上加以修改、添加和删除一些命令。

通过CMenu *pMenu=CWnd::GetSystemMenu(FALSE);即可获取系统菜单,然后可以对pMenu菜单指针进一步操作了。

(二)、通过编程软件visual studio中的资源视图中创建菜单:

这个比较简单易懂,进入编程环境中的“资源视图”中,右键“添加资源”,选择“Menu",即可开始手工创建工菜单。

直接上图:

(三)、编程创建菜单 

使用CMenu::CreateMenu,CMenu::CreatePopupMenu();CMenu::AppendMenu()可以编程创建菜单。

CMenu mainMenu;//创建一个CMenu变量,用于指向顶级菜单。

mainMenu.CreateMenu();//创建一个空的顶级菜单

CMenu popupMenu;//创建一个CMenu变量,用于指向弹出菜单——子菜单。

popupMenu.CreatePopupMenu();//创建一个空的弹出菜单——子菜单。

popupMenu.AppendMenu(MF_STRING,ID_EDIT_COPY,_T("&Copy"));//添加一个菜单子项。

popupMenu.AppendMenu(MF_STRING,ID_EDIT_CUT,_T("C&ut"));//添加一个菜单子项。

popupMenu.AppendMenu(MF_SEPARATOR);//添加一个菜单子项:水平分隔线。

mainMenu.AppendMenu(MF_POPUP,(UINT_PTR)popupMenu.detach(),_T("&Edit"));//在顶级菜单中挂载一个弹出菜单,其名为:Edit。//有些版本是强制转化为UINT

popupMenu.AppendMenu(MF_STRING,ID_FILE_NEW,_T("&New"));//添加一个菜单子项。

popupMenu.AppendMenu(MF_STRING,ID_FILE_OPEN,_T("&Open"));//添加一个菜单子项。

 

mainMenu.AppendMenu(MF_POPUP,(UINT_PTR)popupMenu.detach(),_T("&File));//有些版本是强制转化为UINT

setMenu(&mainMenu);//把菜单挂载到框架窗口上。

mainMenu.detach();//及时分离,防止因变量mainMenu的生命周期结束而影响菜单,如果不detatch,要影响到菜单不能正确显示。

菜单到此,编程的代码基本完成了。但是,上述代码中涉及的各种“ID”,需要在程序资源里面全部定义为常量才算全部完成。

ID_FILE_NEW,ID_FILE_OPEN,ID_FILE_EXIT,ID_EDIT_COPY,ID_EDIT_CUT等是系统已经定义的ID,不需要重新定义(当然,正常情况下不要使用这些ID)。如果ID为自定义的,需要你自己定义为常量。

 

二、菜单的挂载

有了菜单,当然要挂载到框架窗口上面。挂载的方法有以下几种:

(一)框架窗口创建时挂载

CFrameWnd::Create

BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle = WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL, LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext = NULL );

使用这种方法挂载菜单时,菜单一般为拥有 ID,不能直接在此函数中使用。这时需要用到 MAKEINTRESOURCE()宏,其参数为WORD,返回值为:LPTSTR,这就能在函数中使用此菜单参数了。

(二)使用框架窗口类的虚函数CFrameWnd::LoadFrame()挂载菜单

CFrameWnd::LoadFrame

virtual BOOL LoadFrame( UINT nIDResource, DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, CWnd* pParentWnd = NULL, CCreateContext* pContext = NULL );

此函数直接使用菜单的ID挂载。

(二)使用窗口类的SetMenu()函数挂载菜单

CWnd::SetMenu

BOOL SetMenu( CMenu* pMenu );

此函数要使用菜单指针,所以多用于编程形成的菜单的挂载。

三、菜单的修改

(一)菜单的插入

CMenu::InsertMenu

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT_PTR nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

菜单的插入,使用CMenu::InsertMenu就可以了。怎样使用它呢?

假设程序的现有菜单如下图所示:

现在我们可以这样插入:

1、插入“分隔线”:

 插入的位置在File菜单的第2个菜单项之后:

CMenu *pMenu=GetMenu();//获取当前菜单指针

pMenu->GetSubMenu(0)->InsertMenu(2,MF_BYPOSITION|MF_SEPARATOR);//在第一个子菜单第3个菜单项之前插入“分隔线”

第一个参数nPosition 要参照第二个参数nFlags的设置来确定含义。当nFlags包含MF_BYPOSITION的值时,nPosition 就为基于0开始的索引值。如上面语句中,2即为第三个菜单项FileThree的索引(从0开始的索引)值。

nFlags缺省为MF_STRING的值。如果设置包含MF_SEPARATOR,第三、四两个参数就忽略。如上所示。因此,nFlags既包含对nPostion的解释的设置,又包含对后面插入对象的设置。二者之间用逻辑符号 | 连结。

如果nFlag=MF_BYCOMMAND|MF_SEPARATOR的设置,则nPostion应为菜单项的 ID值,而不是基于位置0的索引。

效果图如下:

2、插入新的菜单项:

CMenu *pMenu=GetMenu();//获取当前菜单指针

pMenu->GetSubMenu(0)->InsertMenu(ID_FILE_FILEONE,MF_BYCOMMAND|MF_STRING,0X8001,_T("File000"));

其中,ID_FILE_FILEONE为菜单FileOne的ID。即要此项前插入一个新的菜单项:名字为“File000",ID为0x8001。效果图如下:

 

3、插入已经存在的弹出菜单为子菜单:

CMenu *pMenu=GetMenu();//获取当前菜单指针

CString strMenu;

pMenu->GetMenuString(1,strMenu,MF_BYPOSTION);//获取第2个子菜单(弹出菜单)的名称。

pMenu->GetSubMenu(0)->InsertMenu(

     0,  //插入的位置是基于0的索引,不是ID。与第二个参数设置为MF_BYPOSITION有关。

     MF_BYPOSITION|MF_POPUP,       //设置插入的类型为”弹出菜单”,与前两种不一样。

     (UINT_PTR)(pMenu->GetSubMenu(1)->m_hMenu), //插入的菜单的句柄

     strMenu  //插入后菜单仍然为原来的名字

);

效果如下:

这是把第二个菜单Edit的全部内容挂载到第一个菜单File下面的第一个位置,并作为其子菜单。

实现这个效果,需要在nFlags选项设置MF_POPUP,然后在第三个参数中设为要插入的子菜单的菜单句柄。菜单句柄可以通过菜单对象的成员变量m_hMenu获取。然后转换为UINT_PTR类型即可。第四个参数为菜单插入后的名字。(可以新取一个名字,也可以通过CMenu::GetMenuString()去获取原来的名字。

(二)修改菜单:

CMenu::ModifyMenu

BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT_PTR nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

1、把菜单项修改成“弹出菜单”为子菜单:

CMenu *pMenu = GetMenu();
        pMenu->GetSubMenu(0)->ModifyMenu(
            0,   //基于0的索引
            MF_BYPOSITION | MF_POPUP,  //基于位置,插入的类型为MF_POPUP(弹出菜单)
            (UINT_PTR)(pMenu->GetSubMenu(1)->m_hMenu),  //要插入的菜单的句柄并转换为UINT_PTR
            _T("New")         //插入的子菜单名称为新建的,不是原来的(也可以保留原来的)
        );

 

注意:修改菜单时,原来的旧菜单项被替换。如上面:FileOne菜单项就被替换为弹出菜单了。

2、修改原来的菜单为“分隔线”:

CMenu *pMenu=GetMenu();

pMenu->GetSubMenu(0)->ModifyMenu(

      1,

       MF_BYPOSITION|MF_SEPARATOR

);

效果如下:

可以看到,File下面的第2个菜单项FileTwo被替换为了“分隔线”。

3、修改菜单为新的菜单项(或改名或修改ID,或二者兼改)

CMenu *pMenu=GetMenu();

pMenu->GetSubMenu(0)->ModifyMenu(

      2,

       MF_BYPOSITION|MF_STRING,

       0x8002,

       _T("NEWITEM")

);

效果图如下:

注意:File第3个菜单项FileThree已经被替换为:NewItem了。同时,其ID值也变了。

 

(三)删除菜单

CMenu::RemoveMenu

BOOL RemoveMenu( UINT nPosition, UINT nFlags );

CMenu::DeleteMenu

BOOL DeleteMenu( UINT nPosition, UINT nFlags );

这两个函数用法差不多,如下:

pMenu->GetSubMenu(1)->RemoveMenu(0, MF_BYPOSITION);

pMenu->GetSubMenu(1)->DeleteMenu(0, MF_BYPOSITION);

第二个参数如果是MF_BYCOMMAND时,第一个参数就要设置为菜单项的ID了。

两个函数还是有区别的:DeleteMenu不但删除对应的菜单项,如果此项有子菜单,除了菜单项之外,它还要删除子菜单。而

RemoveMenu只删除菜单项,会把子菜单留在内存中。以实际代码为例:

CMenu *pMenu = GetMenu();
        pMenu->GetSubMenu(0)->InsertMenuW(0, 
            MF_BYPOSITION|MF_POPUP,
            (UINT_PTR)(pMenu->GetSubMenu(1)->m_hMenu),
            _T("New")
        );
        pMenu->GetSubMenu(0)->
DeleteMenu(0, MF_BYPOSITION);

        //pMenu->GetSubMenu(0)->RemoveMenu(0, MF_BYPOSITION);

以上代码,当使用DeleteMenu时,程序第二菜单不能正常使用,会显示错误。因为DeleteMenu已经把子菜单Edit从内存中删除了;如果DeleteMenu换成RemoveMenu,则Edit菜单依然可以正常使用。请年效果图:

使用RemoveMenu时:

使用DeleteMenu时:

因为DeleteMenu会从内存中删除Edit子菜单,所以程序立刻出现错误了。

四、菜单的自定义

菜单的自定义是指菜单的外观显示不是由程序自动更新维护的,而是由你自己手动绘制并更新的。

首先,一般的菜单,外观都是文字显示的。其实,菜单也可以以图形显示。

CMenu *pMenu = GetMenu();
        CBitmap bitmap;
        bitmap.LoadBitmapW(IDB_CAI);
        pMenu->GetSubMenu(0)->ModifyMenuW(
            0,
            MF_BYPOSITION,
            ID_FILE_FILEONE,
            &bitmap
        );

上面的代码,是把菜单File中的第一个菜单项FileOne修改成位图显示。一个名为“IDB_CAI"的位图被显示在原来菜单项的位置。效果图如下:

特别要注意:位图变量bitmap最好定义为框架窗口的成员变量,而不能只定义在窗口的某一个函数中的局部变量。因为一旦bitmap变量的生命周期短于窗口,它的生命周期一结束,菜单将不能正常显示,菜单位置将出现空白显示。

上面的菜单项虽然是图形显示的,但它仍然是由程序自动绘制并更新的。其实菜单的绘制与更新。还可以由你自定义,也就是说,由你自己绘制并更新。这就是菜单的自定义,不管绘制的是文字、符号还是位图,都由你决定并实施。

实现菜单自定义,要通过以下几步才能实现:

(一)第一步:使用ModifyMenu()修改菜单风格,由程序自动绘制变为自己绘制。

在CWinApp::InitInstance()成员函数中修改菜单项(只一项,也可以全部修改)风格为自绘制:MF_OWNERDRAW。

CMenu *pMenu = m_pMainWnd->GetMenu();
    pMenu->GetSubMenu(0)->ModifyMenuW(
        0,
        MF_BYPOSITION | MF_OWNERDRAW,
        ID_FILE_FILEONE
    );

这样,FileOne这个菜单项将不能自动绘制并显示了,而由你自己处理绘制过程。

(二)第二步:处理WM_MEASUREITEM消息,并在OnMeasureItem消息处理函数中处理与绘制菜单相关的信息。

CWnd::OnMeasureItem 

afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );

菜单的自绘制,首先要通过响应这个消息函数确定经绘制的菜单的宽度和高度值。

在这个消息处理函数中,如果是因菜单自绘制引起的消息,则参数nIDCtl没有用,因为它是响应控件自绘制时使用的。

这个消息处理函数涉及到一个结构:MEASUREITEMSTRUCT结构。其原型为:

typedef struct tagMEASUREITEMSTRUCT {UINT      CtlType; UINT      CtlID; UINT      itemID; UINT      itemWidth; UINT      itemHeight; ULONG_PTR itemData; 
} MEASUREITEMSTRUCT; 

其中,CtrlType为消息引发的对象的类型。如果是菜单自绘制引起的,它的值为:ODT_MENU。当然,它还有其它值:

ODT_BUTTONOwner-drawn button
ODT_COMBOBOXOwner-drawn combo box
ODT_LISTBOXOwner-drawn list box
ODT_LISTVIEWOwner-draw list view control

这些值是引发消息的其它对象:如CButton……等。这里暂时不讨论。

如果是菜单自绘制引起的,这个结构中:CtlID和itemData不能使用。

itemID是要绘制的菜单的ID,可以使用。当要自绘制的菜单不止一项时,用它区别不能的菜单项。

这里的重点是:itemWidth和itemHeight两个变量。它告诉我们要绘制的菜单的宽度和高度值。这是绘制菜单必须首先知道的。

怎么确定这个值呢?最好使用以下办法:

lpmis->itemWidth=::GetSystemMetrics(SM_CYMENU)*4;

lpmis->itemHeight=::GetSystemMetrics(SM_CYMENU)*2;//正常时候不需要*2

 SM_CYMENU是系统给顶级菜单画的菜单栏的高度。通过在该值的基础上确定自绘制菜单的宽度和高度值,能保证自绘制菜单项与Windows画的菜单项的尺寸比例大致协调。

(三)第三步:也是最后一步,处理WM_DRAWITEM消息,并在OnDrawItem()消息处理函数中完成实际绘制菜单。

CWnd::OnDrawItem 

afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );

这个消息处理函数涉及一个结构:DRAWITEMSTRUCT,其原型如下:

typedef struct tagDRAWITEMSTRUCT {UINT      CtlType; UINT      CtlID; UINT      itemID; UINT      itemAction; UINT      itemState; HWND      hwndItem; HDC       hDC; RECT      rcItem; ULONG_PTR itemData; 
} DRAWITEMSTRUCT; 

同样,如果消息是菜单自绘制引起的,CtlID和itemData没有用。

itemID仍然为经要绘制的菜单项的ID;

hDC是要绘制的菜单项所在的设备描述表的句柄。

rcItem是包括菜单项所在的矩形的坐标。你要绘制的内容就在此矩形范围内完成的。

hwndItem是包含要绘制的菜单项的菜单的句柄。

itemState是描述要绘制的菜单项的状态:选中还是没选中,有效还是无效……

itemAction是绘制的动作。有ODA_DRAWENTIRE,要求菜单要全部绘制;也可以是ODA_SELECT,绘制菜单选择状态或失去选择状态;ODA_FOCUS,绘制菜单获得焦点或失去焦点的状态。

弄明白以上参数的含义后,我们需要明白:菜单要绘制的内容有哪些?

它应该包括两个内容:一是菜单项本身要显示的内容;二是菜单项的状态信息要表现出来(绘制菜单的状态),比如是否处于选择状态,是否处于高亮状态等等。

假设我们的菜单本身是一个位图,并处于选择和高亮状态,怎样实现绘制呢?代码如下:

        BITMAP bm;CBitmap bitmap;bitmap.LoadOEMBitmap(OBM_CHECK);//系统内定义的菜单处于复选状态的位图:勾。bitmap.GetObject(sizeof(bm), &bm);//获得此位图的相关信息给bm:宽度、高度、颜色等。CDC dc;dc.Attach(lpDrawItemStruct->hDC);//从DRAWITEMSTRUCT结构中的成员变量hDC句柄得到CDC//绘制菜单的高亮状态或正常状态CBrush *pBrush = new CBrush(::GetSysColor((lpDrawItemStruct->itemState&ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU));//绘制一个矩形框,矩形就是菜单绘制的区域dc.FrameRect(&(lpDrawItemStruct->rcItem), pBrush);delete pBrush;//使用CDC::BitBlt()绘制菜单的“复选”状态。CDC dcMem;dcMem.CreateCompatibleDC(&dc);//构造一个内存CDC,并与菜单的dc相适应。CBitmap *pOldBitmap=dcMem.SelectObject(&bitmap);//把位图选择进dcMem并保存以前的位图dc.BitBlt(lpDrawItemStruct->rcItem.left + 4,lpDrawItemStruct->rcItem.top + (((lpDrawItemStruct->rcItem.bottom -lpDrawItemStruct->rcItem.top) - bm.bmHeight) / 2),bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);dcMem.SelectObject(pOldBitmap);//绘制菜单本身显示的内容,这里它是一个位图:IDB_CAIbitmap.DeleteObject();bitmap.LoadBitmapW(IDB_CAI);bitmap.GetObject(sizeof(bm), &bm);pOldBitmap=dcMem.SelectObject(&bitmap);dc.BitBlt(lpDrawItemStruct->rcItem.left + 4 + bm.bmWidth + 4,lpDrawItemStruct->rcItem.top+1,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);dcMem.SelectObject(pOldBitmap);dc.Detach();//使用了Attach(),不用时就要Detach()。

效果如下:

特别提醒:复选状态应该根据菜单项自身是否处于复选状态来确定绘制与否。如:

if (lpDrawItemStruct->itemState&ODS_CHECKED) {

}

上述代码没有加上这个条件,是因为菜单还没有加上维护菜单状态的代码,菜单状态无法正确及时地更新,所以直接绘制为复选状态了。正常情况下不应该这样。

到此,菜单的自定义绘制就完成了!!!

 

五、菜单命令的响应

单击菜单命令时,会产生菜单消息。菜单消息分为两类:

系统菜单:产生WM_SYSCOMMAND消息;

一般菜单和上下文菜单(右键产生的菜单),产生的是WM_COMMAND消息。

 

 

为了执行菜单命令,需要映射菜单产生的消息及消息处理函数。比如:

消息映射:ON_COMMAND(ID_FILE_FILEONE, &CMainFrame::OnFileFileone)

第一个参数为菜单命令的ID,第二个为消息处理函数。

消息处理函数:void OnFileFileone() 它没有参数。然后在消息处理函数中处理消息产生后要完成的工作。

 

系统菜单的消息映射为:ON_WM_SYSCOMMAND()

系统消息处理函数:void OnSysCommand(UINT nID, LPARAM lParam)

参数nID是命令的类型,为以下值之一:

SC_CLOSE   Close the CWnd object.
 

SC_HOTKEY   Activate the CWnd object associated with the application-specified hot key. The low-order word of lParam identifies the HWND of the window to activate.
 

SC_HSCROLL   Scroll horizontally.
 

SC_KEYMENU   Retrieve a menu through a keystroke.
 

SC_MAXIMIZE (or SC_ZOOM)   Maximize the CWnd object.
 

SC_MINIMIZE (or SC_ICON)   Minimize the CWnd object.
 

SC_MOUSEMENU   Retrieve a menu through a mouse click.
 

SC_MOVE   Move the CWnd object.
 

SC_NEXTWINDOW   Move to the next window.
 

SC_PREVWINDOW   Move to the previous window.
 

SC_RESTORE   Restore window to normal position and size.
 

SC_SCREENSAVE   Executes the screen-saver application specified in the [boot] section of the SYSTEM.INI file.
 

SC_SIZE   Size the CWnd object.
 

SC_TASKLIST   Execute or activate the Windows Task Manager application.
 

SC_VSCROLL   Scroll vertically.

菜单命令是鼠标选择时,参数lParam鼠标的x,y坐标。低阶字包含x坐标,高阶字包含y坐标。否则,不使用此参数。

 

一个菜单命令映射一个函数,但如果几个菜单命令之间有关联,它们的ID号也是相连的,这种情况也可以把这几个命令映射到同一个函数来处理。如:

ON_COMMAND(ID_FILE_FILEONE,OnFile)

ON_COMMAND(ID_FILE_FILETWO,OnFile)

ON_COMMAND(ID_FILE_FILETHREE,OnFile)

它们共同的消息处理函数为:

void OnFile() 函数没有参数。

那如何分别是哪一个命令产生的消息呢?这个就要借助CWnd::GetCurrentMessage 函数了:

UINT nID=(UINT)LOWORD(GetCurrentMessage()->wParam);

然后根据菜单ID与nID比较,就能知道是哪个命令产生的消息了。

 

上面这种情况,还有更好的解决方案。

ON_WM_COMMAND_RANGE(ID_FILE_FILEONE,ID_FILE_FILETHREE,OnFile)

其映射的函数为:

void OnFile(UINT nID),其中,nID为命令消息产生的菜单ID。根据此ID在消息函数中完成需要完成的任务。

 

还有一种情况比较特殊:当程序在上下文中使用的弹出菜单为系统菜单时,单击菜单项产生的消息不是期望的:

WM_SYSCOMMAND消息,而是WM_COMMAND消息。怎么办呢?

void CMainWindow::OnContextMenu (CWnd* pWnd, CPoint point)
{
    CRect rect;
    GetClientRect (&rect);
    ClientToScreen (&rect);


    if (rect.PtInRect (point)) {

        CMenu* pMenu = GetSystemMenu (FALSE);       //获取系统菜单
        UpdateSystemMenu (pMenu);

        int nID = (int) pMenu->TrackPopupMenu (TPM_LEFTALIGN |
            TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x,
            point.y, this);

        if (nID > 0)
           
SendMessage (WM_SYSCOMMAND, nID, 0);

        return;
    }
    CFrameWnd::OnContextMenu (pWnd, point);

}

借助SendMessage()或PostMessage()实现WM_SYSCOMMAND消息的产生。

下面这个函数是手动更新系统菜单命令的内容:因为系统菜单变为弹出菜单时,其状态不能及时更新。

要进行以下设置,需要先设:

CFrameWnd::m_bAutoMenuEnable =FALSE;不让程序自动更新菜单的状态。
void CMainWindow::UpdateSystemMenu (CMenu* pMenu)
{
    static UINT nState[2][5] = {
        { MFS_GRAYED,  MFS_ENABLED, MFS_ENABLED,
          MFS_ENABLED, MFS_DEFAULT },
        { MFS_DEFAULT, MFS_GRAYED,  MFS_GRAYED,
          MFS_ENABLED, MFS_GRAYED  }
    };

    if (IsIconic ()) // Shouldn't happen, but let's be safe
        return;

    int i = 0;
    if (IsZoomed ())
        i = 1;

    CString strMenuText;
    pMenu->GetMenuString (SC_RESTORE, strMenuText, MF_BYCOMMAND);
    pMenu->ModifyMenu (SC_RESTORE, MF_STRING | nState[i][0], SC_RESTORE,
        strMenuText);

    pMenu->GetMenuString (SC_MOVE, strMenuText, MF_BYCOMMAND);
    pMenu->ModifyMenu (SC_MOVE, MF_STRING | nState[i][1], SC_MOVE,
        strMenuText);

    pMenu->GetMenuString (SC_SIZE, strMenuText, MF_BYCOMMAND);
    pMenu->ModifyMenu (SC_SIZE, MF_STRING | nState[i][2], SC_SIZE,
        strMenuText);


    pMenu->GetMenuString (SC_MINIMIZE, strMenuText, MF_BYCOMMAND);
    pMenu->ModifyMenu (SC_MINIMIZE, MF_STRING | nState[i][3], SC_MINIMIZE,
        strMenuText);

    pMenu->GetMenuString (SC_MAXIMIZE, strMenuText, MF_BYCOMMAND);
    pMenu->ModifyMenu (SC_MAXIMIZE, MF_STRING | nState[i][4], SC_MAXIMIZE,
        strMenuText);

    SetMenuDefaultItem (pMenu->m_hMenu, i ? SC_RESTORE :
        SC_MAXIMIZE, FALSE);
}

 

六、菜单命令的状态更新与维护

首先应该明白菜单命令的状态有哪些?菜单命令状态的变化与什么因素相关?改变菜单状态变化的方法有哪些?

(一)菜单的状态:

1、有效或无效状态;

2、选择或未选择状态;

3、复选或未复选状态;

4、单选或未单选状态;

(二)菜单状态变化的因素:

1、菜单是否有效:

如果CFrameWnd::m_bAutoMenuEnable =TRUE,程序内部有自动更新机制。如:菜单没有自动更新函数ON_UPDATE_COMMAND_UI(),菜单会自动无效。

如果CFrameWnd::m_bAutoMenuEnable =FALSE,菜单是否有效,需要根据程序的要求,手动更新。

以系统菜单为例:当框架窗口最大化时,系统菜单的:最大化、大小、移动等三个命令应该处于“无效”状态;此时,“恢复”命令应该处于“有效”状态;当不是最大化时,“恢复”命令应该无效,其它所有命令都是“有效”的!如图:

最大化时系统菜单命令的状态
窗口最大化时系统菜单命令的状态

 

窗口非最大化时的菜单命令的状态

因此,菜单命令的有效与否,除部分由系统决定,自动更新以外,更多时候由程序根据自己的需要,设置有效与否的条件,然后根据此条件手动更新。

2、是否处于选择状态:

当鼠标指向某个菜单项命令时,当前菜单项就是处于:选择状态;其它菜单命令就是未选择状态。当鼠标从当前菜单命令移开并指向另一个菜单命令时,被指向的菜单处于选择状态,其它所有菜单命令处于未选择状态。

3、是否处于复选状态:

是否复选状态,完全取决于程序自己的要求以及根据要求设置的条件来决定。复选从含义上说,处于“多选”状态,就是有几个命令同时处于选择状态(与选择不是上述的鼠标选择状态)。

4、是否处于单选状态:

有些时候,几个菜单命令之间是相关联的,但其中只能选择一个,此时就是“单选状态”确定的时候。比如:当我们从菜单选择不同的颜色命令时,不可能同时有多种颜色被选择,只能是任选其一。

(三)改变菜单状态的方法:

1、菜单的有效与否:

(1)由系统自动更新(缺省的)

(2)由程序手动更新

CFrameWnd::m_bAutoMenuEnable =FALSE时。

具体更新的方法有以下几种:

 

CMenu::EnableMenuItem

UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );

如以下代码:

BOOL SimpleApp::InitInstance()
{
    // TODO: 在此添加专用代码和/或调用基类
    
    m_pMainWnd = new CMainFrame();
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();

    CMenu* pMenu = ((CMainFrame *)m_pMainWnd)->GetMenu();
    pMenu->GetSubMenu(0)->EnableMenuItem(ID_FILE_FILEONE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);

 

    return CWinApp::InitInstance();
}


    

下面这个函数用于WM_UPDATE_COMMAND_UI消息处理函数中。

CCmdUI::Enable

virtual void Enable( BOOL bOn = TRUE );

如以下代码:

void CMainFrame::OnUpdateFileFileone(CCmdUI *pCmdUI)
{
    // TODO: 在此添加命令更新用户界面处理程序代码
    
  
 pCmdUI->Enable(FALSE);
}
 

 

还有一个方法也能修改菜单是否有效

CMenu::ModifyMenu

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

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

如果有以下代码:

CMenu *pMenu = m_pMainWnd->GetMenu();
CString strMenu;

pMenu->GetSubMenu(0)->GetMenuStringW(0, strMenu, MF_BYPOSITION);
pMenu->GetSubMenu(0)->ModifyMenuW(
        0,
        MF_BYPOSITION|MFS_DISABLED ,
        ID_FILE_FILEONE,
        strMenu
    );

以上几种代码,结果都如下图,File菜单下的第一个命令项就变为“不可用”状态了。

2、菜单是否处于被选择状态:

这个状态,一般情况下是由程序自动更新的,不需要去处理它。

但如果菜单处于MF_OWERDRAW风格,在响应WM_DRAWITEM消息时,菜单的选择状态需要程序在消息处理函数OnDrawItem()中手动绘制的。如以下代码:

void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    

    CDC dc;
    dc.Attach(lpDrawItemStruct->hDC);//从DRAWITEMSTRUCT结构中的成员变量hDC得到CDC
    //绘制菜单的高亮状态或正常状态
    CBrush *pBrush = new CBrush(
        ::GetSysColor(
        (lpDrawItemStruct->itemState&ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU
        )

    );
    dc.FrameRect(&(lpDrawItemStruct->rcItem), pBrush);
    delete pBrush;
    
}

3、菜单是否处于复选状态:

在visual studio编程软件中,在资源视图中编辑菜单时,可以选择菜单命令处于“复选”状态。如下图:

另外:

CMenu::CheckMenuItem

UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );

也是可以设置“复选”状态的。

    CMenu* pMenu = ((CMainFrame *)m_pMainWnd)->GetMenu();
    pMenu->GetSubMenu(1)->CheckMenuItem(ID_FILE_FILEONE, MF_BYCOMMAND | MFS_CHECKED);

 

同样,

CMenu::ModifyMenu

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

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

也是可以修改复选状态的:

BOOL SimpleApp::InitInstance()
{
    // TODO: 在此添加专用代码和/或调用基类
    
    m_pMainWnd = new CMainFrame();
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();


    CMenu *pMenu = m_pMainWnd->GetMenu();
    CString strMenu;
    pMenu->GetSubMenu(0)->GetMenuStringW(0, strMenu, MF_BYPOSITION);
    pMenu->GetSubMenu(0)->ModifyMenuW(
        0,
        MF_BYPOSITION|MFS_CHECKED ,
        ID_FILE_FILEONE,
        strMenu
    );

    return CWinApp::InitInstance();
}

 

还有一个更新复选状态的地方:WM_UPDATE_COMMAND_UI消息产生时,在其消息处理函数中也可以设置“复选”状态:

void CMainFrame::OnUpdateFileFileone(CCmdUI *pCmdUI)
{
    // TODO: 在此添加命令更新用户界面处理程序代码
    
    pCmdUI->SetCheck(TRUE);
}

 

 

如果菜单是自绘制的,那么在响应WM_DRAWITEM消息的消息处理函数OnDrawItem()中,需要程序手动绘制菜单的复选状态的:

void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

 

    BITMAP bm;
    CBitmap bitmap;
    bitmap.LoadOEMBitmap(OBM_CHECK);//系统内定义的菜单处于选择状态的位图:勾。
    bitmap.GetObject(sizeof(bm), &bm);//获得此位图的相关信息给bm:宽度、高度、颜色等。

    CDC dc;
    dc.Attach(lpDrawItemStruct->hDC);//从DRAWITEMSTRUCT结构中的成员变量hDC得到CDC
   

    //使用CDC::BitBlt()绘制菜单的“复选”状态。
    CDC dcMem;
    dcMem.CreateCompatibleDC(&dc);//构造一个内存CDC,并与菜单的dc相适应。
    CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);//把位图选择进dcMem并保存以前的位图
    if (lpDrawItemStruct->itemState&ODS_CHECKED) {

        dc.BitBlt(
            lpDrawItemStruct->rcItem.left + 4,
            lpDrawItemStruct->rcItem.top + (((lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top) - bm.bmHeight) / 2),
            bm.bmWidth,
            bm.bmHeight,
            &dcMem,
            0,
            0,
            SRCCOPY
        );

        dcMem.SelectObject(pOldBitmap);


    
}

 

4、菜单是否处于单选状态:

菜单处于单选状态时,没有一个系统定义的常量代号与之对应。也就是说,菜单处于复选时,对应的状态应该是MF_CHECKED

其值为8;处于单选状态时,其值是520,即十六进制的0x208。怎么得来的呢?看看系统定义的常量:

#define MF_UNCHECKED        0x00000000L
#define MF_CHECKED          0x00000008L
#define MF_USECHECKBITMAPS  0x00000200L

可以看出:单选状态时,是MF_CHECKED|MF_USECHECKBITMAPS=0x208=520。

因此判断是否处于单选状态应该依据状态值是否=MF_CHECKED|MF_USECHECKBITMAPS。

怎样才能设置单选状态呢?

CMenu::CheckMenuRadioItem

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

CCmdUI::SetRadio

virtual void SetRadio( BOOL bOn = TRUE );

以上两个函数都能设置“单选”状态。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


http://chatgpt.dhexx.cn/article/u7V0eUAg.shtml

相关文章

Windows 编程[18] - 更换菜单项: ModifyMenu

为什么80%的码农都做不了架构师?>>> 本例效果图: 本例使用的资源文件(TestRes.rc): MyMenu1 MENUEX BEGINMENUITEM "Open" ,101MENUITEM "Old" ,102MENUITEM "Help" ,103 END本例代码文件: program Project1;{$R Tes…

动态更改菜单之ModifyMenu

多语言支持,已经是桌面应用软件的发展趋势。用句流行的词就是“国际化”。除了语言翻译的工作外,比较重要的就是字符的编码问题。不过这些问题的讨论已经铺天盖地了,我就不想再多说细节了。我在此只记录一下我的软件“国际化”中的一个技术小…

Android 为CheckBoxPreference Preference 控件添加样式

Android 为CheckBoxPreference Preference 控件添加样式 分类&#xff1a; android 2012-11-16 17:48 8920人阅读 评论(4) 收藏 举报 [html] view plain copy print ? <CheckBoxPreference android:key"enable_adb" android:layout"…

数据存储: CheckBoxPreference

最近&#xff0c;做一个项目&#xff0c;使用 Preference 非常频繁&#xff0c;之前就很想写一些关于 android 数据存储的文章&#xff0c;这次机会来了。 先从小出着手吧&#xff0c;CheckBoxPreference 小巧可爱&#xff0c;使用简单。但是使用的时候&#xff0c;还是需要注意…

CheckBoxPreference组件

CheckBoxPreference 选中为true 取消选中为false 它的值会以boolean的形式储存在SharedPreferences中。 <?xml version"1.0" encoding"utf-8"?><PreferenceScreenxmlns:android"http://schemas.android.com/apk/res/android"><…

Android 自定义CheckBoxPreference的CheckBox复选框

在使用Android的Preference&#xff0c;有时为了让我们的界面更加美观&#xff0c;我们会自定义自己的Preference。今天就主要说一下怎样自定义CheckBoxPreference的CheckBox按钮。 系统默认CheckBoxPreference的CheckBox样式 自定义后的CheckBox样式 其实&#xff0c;关键的一…

最好用的手机端C/C++语言编程软件, 不要说没电脑就不学编程了!

今天介绍一个软件—C编译器(c4droid)&#xff0c;可以直接编辑运行C/C程序&#xff0c;代码高亮、语法检查&#xff0c;使用起来非常不错&#xff0c;下面我简单介绍一下这个软件的安装和使用&#xff1a; 安装C编译器&#xff0c;这个直接在手机应用中搜索就行&#xff0c;如…

最新版手机端C/C++语言编程的软件

今天介绍一个软件—C编译器(c4droid)&#xff0c;可以直接编辑运行C/C程序&#xff0c;代码高亮、语法检查&#xff0c;使用起来非常不错&#xff0c;下面我简单介绍一下这个软件的安装和使用&#xff1a; 安装C编译器&#xff0c;这个直接在手机应用中搜索就行&#xff0c;如…

C语言编程工具软件推荐

c语言编程软件适于编写系统软件&#xff0c;是学习编程的同学们的必备软件。c语言一种非常强大的计算机语言&#xff0c;应用非常广泛&#xff0c;不仅仅是在软件开发上&#xff0c;而且各类科研都会用到c语言。今天小编给大家汇总下C语言的编程工具 中国有句古话叫做“工欲善其…

c语言编程软件有哪些 Win7下用哪种C语言编译器

C语言是一门历史很长的编程语言&#xff0c;其编译器和开发工具也多种多样&#xff0c;其开发工具包括编译器&#xff0c;现举几个开发工具供大家选择&#xff0c;当然也要根据自己的操作系统来选择适合自己的开发工具 好多刚开始接触c语言的朋友都想知道用上面软件开发c语言…

手机上可以编程看代码的软件

以后大家会在路上看到很多人拿着手机,他不是在聊天,他有可能是运维工程师、也可能是算法开发、也可能是java开发,还可能是客户端开发,也可能是前端开发... 让你编程一直在路上,这到底是程序员的福音,还是码农的枷锁。 粉丝提问: 这里介绍几款可以在手机上编程的app,分…

学习c语言编程用什么软件_用C编程

学习c语言编程用什么软件 We would start writing basic programs in C now. You need to have required software installed and configured in your system. Refer to the article of Hello World and ensure that you are able to compile and run the program. 我们现在就开…

怎样查看C语言的程序内容,什么手机软件能看c语言文件?

C语言编译器和C编译器这2个手机软件就可以&#xff0c;而且还可以直接运行C语言程序&#xff0c;使用起来非常方便&#xff0c;下面我简单介绍一下这2个软件的安装和使用&#xff0c;感兴趣的朋友可以在自己手机上尝试一下&#xff1a; C语言编译器 这是手机上一个纯粹的C语言编…

c语言编程开发app,C语言编程app

C语言编程是一款掌上C语言学习软件&#xff0c;平台为用户提供专业的C语言学习资源&#xff0c;用户可以随时在线进行刷题学习&#xff0c;还能将错题记录和题目收藏&#xff0c;方便用户进行针对训练&#xff0c;轻松备考计算机二级&#xff0c;有需要的朋友快来下载吧&#x…

C语言编译器(C语言编程软件)

桌面操作系统 对于当前主流桌面操作系统而言&#xff0c;可使用 Visual C、GCC 以及 LLVM Clang 这三大编译器。 Visual C&#xff08;简称 MSVC&#xff09;是由微软开发的&#xff0c;只能用于 Windows 操作系统&#xff1b;GCC 和 LLVM Clang 除了可用于 Windows 操作系统…

c语言程序设计网站有哪些,C语言编程软件有哪些?

C语言是一门通用计算机编程语言&#xff0c;应用广泛。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。 尽管C语言提供了许多低级处理的功能&#xff0c;但仍然保持着良好跨平台的特性&#xf…

最火的C语言编程软件,适合编写C语言代码的编程软件有哪些

C语言基本上是大学计算机及其相关专业在大一上学期就会开的一门课程,但是很多学生就是在大一上学期期末的时候很着急,因为自己完全没有学好C语言,感觉一学期白学了,其实究其主要原因,还是因为你在上课认真听了,也做了课堂作业,但是却没有在课后好好的自己去主动敲代码,…

常用的C语言编程工具

中国有句古话叫做“工欲善其事&#xff0c;必先利其器”&#xff0c;可见我们对工具的利用是从祖辈就传下来的&#xff0c;而且也告诉我们在开始做事之前先要把工具准备好。有了好的工具那么我们做起事来也会事半功倍。学习C语言也是一样的&#xff0c;对于初学者来说往往选择一…

手机上做c语言作业的软件下载,c语言编程软件手机版下载-C语言编程 安卓版v1.0.2-PC6安卓网...

C语言编程这是为众多考证用户专门制作的在线学习软件&#xff0c;C语言编程app将考证要用到的相关知识归纳好经过题库的形式来让大家熟练和上手&#xff0c;C语言编程app可以协助大家经过二级计算机考试。 软件介绍 C语言编程是一款掌上C语言学习软件&#xff0c;平台为用户提供…

C语言学习——编程软件安装和使用

C语言学习——软件安装和使用 C语言编程软件适于编写系统软件&#xff0c;是学习编程的同学们的必备软件。今天小编给大家推荐一下自己用的C语言编程软件。 一、Microsoft Visual C 2010 Express &#xff08;国家计算机等级专用&#xff09; 链接:https://pan.baidu.com/s/…