菜单编写(VC)

目录

菜单在 .rc 文件中的格式
加载/卸载菜单
菜单常用的操作
创建弹出菜单
菜单加速键
MFC下菜单消息路由

(本章节中例子都是用 VS2005 编译调试的)


菜单在 .rc 文件中的格式

.rc 中的菜单格式

虽然现在微软的编译器中都会自动生成好用的 rc 资源但是还是可以了解下它内部代码的意义.

这里是不太建议直接在 .rc 文件中修改菜单因为修改了.rc 文件后还需在其他文件中修改对应地方,否则在编译中会报错.所以还是建议在编译器的资源管理器中修改对话框.

格式:

menuID MENU [,载入特性选项]
{
菜单项列表
}

说明:

  • menuID: 菜单资源标识
  • MEMU: 关键字
  • 载入特性: 
    • DISCARDABLE 当不再需要菜单时候菜单可丢弃
    • FIXED 将菜单保存在内存中固定位置
    • LOADONCALL 需要时加载菜单
    • MOVEABLE 菜单在内存中可移动
    • PRELOAD 立即加载菜单
  • 菜单项列表:
    • 弹出菜单/子菜单(POPUP)
      • 格式:
        • POPUP"子菜单名"[,选项]
          BEGIN
          …(菜单项成员)
          END
      • 说明:
        • POPUP:  关键字
        • 子菜单名:  "子菜单的名字&热键"
        • BEGIN:  子菜单中菜单项开始的标识
        • 选项:
          • MENUBARBREAK   菜单项纵向分隔标识
          • CHECKED   显示选中标识
          • INACTIVE   禁止一个菜单项
          • GRAYED   禁止一个菜单项并使其显示灰色
        • 菜单项成员:   子菜单或菜单项(定义如下所示)
        • END:   子菜单中菜单项结束的标识
    • 菜单项(MENUITEM)
      • 格式:  MENUITEM "菜单项名",菜单项标识符(ID)[,选项]
      • 说明:
        • MENUITEM:   关键字
        • 菜单项名:   "菜单项名字&热键"
        • 选项:
          • MENUBARBREAK   菜单项纵向分隔标识
          • CHECKED   显示选中标识
          • INACTIVE   禁止一个菜单项
          • GRAYED   禁止一个菜单项并使其显示灰色 

菜单组成部分

  • 主菜单栏
  • 下拉式菜单框
  • 菜单项热键标识
  • 菜单项加速键标识
  • 菜单项分割线(占据菜单索引)

加载/卸载菜单

加载菜单

在 win32 界面程序中加载菜单有以下几种方式:

  • 在窗口类设计时候进行加载
    在定义 WNDCLASS 时对成员 lpszMenuName 赋予相对应的值
  • 在创建窗口时候进行加载
  • 动态加载菜单

代码示例:

.rc 资源内容

IDR_MENU1 MENU 
BEGIN
    POPUP "菜单1"
    BEGIN
        POPUP "子菜单1.1"
        BEGIN
            MENUITEM "菜单项1.1.1",                    ID_40001
            MENUITEM "菜单项1.2.1",                    ID_40002
        END
        MENUITEM "菜单项1.2",                      ID_40003
        MENUITEM SEPARATOR
        MENUITEM "菜单项1.3",                      ID_40004
        MENUITEM "菜单项1.4",                      ID_40005
    END
    POPUP "菜单2"
    BEGIN
        MENUITEM "菜单项2.1",                      ID_40006
        MENUITEM "菜单项2.2",                      ID_40007
    END
END

加载菜单:

  • 第一种加载方式(类设计时):
    WNDCLASS wndclass;
    
    ....
    wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);
    //这里省略了窗体类创建时需要填写的其他信息.
  • 第二种加载方式(窗体创建时):
    HMENU hmenu;
    WNDCLASS wndclass;
    
    ....
    wndclass.lpszMenuName=NULL;
    //这里省略了一些窗体类的必要信息填写,和注册窗口类等操作
    
    //加载菜单到菜单句柄中
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    //在创建窗体时候载入菜单
    hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);
  • 第三种加载方式(窗体创建后):
    HMENU hmenu;
    WNDCLASS wndclass;
    
    ....
    wndclass.lpszMenuName=NULL;
    //这里省略了一些窗体类的必要信息填写,和注册窗口类等操作
    
    //创建窗体
    hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
    
    //加载菜单到菜单句柄中
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    //动态的加载菜单到窗体中去
    SetMenu(hwnd,hmenu);

程序源码:

View Code
#include<windows.h>
#include"resource.h"

LRESULT CALLBACK textprom(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);

int WINAPI WinMain(  HINSTANCE hInstance,  // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,      // pointer to command line
  int nCmdShow          // show state of window
  )
{
    WNDCLASS wndclass;
    HWND hwnd;
    HMENU hmenu;
    MSG msg;

    //设计窗口类
    wndclass.cbClsExtra=0;
    wndclass.cbWndExtra=0;
    wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndclass.hIcon=LoadIcon(NULL,IDI_ERROR);
    wndclass.hInstance=hInstance;
    wndclass.lpfnWndProc=textprom;
    wndclass.lpszClassName="text";
    wndclass.lpszMenuName=NULL;
    //wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);
    wndclass.style=CS_HREDRAW | CS_VREDRAW;
    
    //注册窗口类
    if(!RegisterClass(&wndclass))
    {
        MessageBox(NULL,"create windows error!","error",MB_OK | MB_ICONSTOP);
    }

    //创建无菜单资源的窗口窗口
    hwnd=CreateWindow("text","hellow world",WS_DLGFRAME | WS_MINIMIZEBOX | WS_SYSMENU,0,0,
        500,300,NULL,NULL,hInstance,NULL);
    
    /*
    //载入菜单资源
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    //创建有菜单资源的窗口
    hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);*/
    
    //载入菜单资源,并在窗口加载菜单资源
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    SetMenu(hwnd,hmenu);

    //显示更新窗口
    ShowWindow(hwnd,nCmdShow);
    UpdateWindow(hwnd);

    //消息循环
    while(GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK textprom(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
    switch(uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

运行结果:

卸载菜单

步骤:

代码示例:

HMENU hmenu;
switch(uMsg)
{
//添加鼠标右键单击事件响应处理,即卸载菜单
case WM_LBUTTONDOWN:
    hmenu = GetMenu(hwnd);
    if(hmenu != NULL)
    {
        SetMenu(hwnd,NULL);
        DestroyMenu(hmenu);
    }
    break;

.....

运行结果:

 

单击鼠标右键后


菜单常用的操作

菜单项常用操作 

  • 设置默认菜单项  SetMenuDefaultItem
  • 禁止或激活菜单项  EnableMenuItem
  • 设置默认菜单项  SetMenuDefaultItem
  • 修改菜单项  ModifyMenu
  • 设置或或取消菜单项选择标志  CheckMenuItem
  • 获得菜单项的 ID     GetMenuItemID 
  • 获得菜单项的标志位    GetMenuState
  • 获得菜单项的字符串     GetMenuString 

菜单常用操作

  • 获取子菜单句柄  GetSubMenu
  • 获得窗口主菜单句柄  GetMenu
  • 动态创建空的弹出菜单  CreateMenu
  • 销毁动态创建空的弹出菜单    DestroyMenu 
  • 删除菜单项  DeleteMenu
  • 在菜单中插入菜单项  InsertMenu
  • 在菜单尾部增加菜单项  AppendMenu
  • 获得弹出菜单的菜单子项的数目    GetMenuItemCounte

菜单索引

在编写菜单操作前还需了解个很重要的概念,即了解菜单的结构,只有了解了这个结构,才能找到对应的菜单,子菜单或菜单项进行对应的操作.因为在操作菜单时需要获得对应的菜单句柄(如在上图子菜单1.1进行插入菜单项1.3.1操作时要获得子菜单1.1句柄),或操作菜单项时要获得对应的子菜单项的菜单句柄(如让上图菜单项1.1.1禁用时候需要获得子菜单1.1的句柄).然而在获取子菜单句柄时需要子菜单索引,所以对索引号的一些规则必须要有一定的了解.

简单规则如下:

    • 菜单索引基于0开始;
    • 分隔符也算一个菜单项,所以他也占据一个索引号

即如下图所示:

两个简单样例:

  • 在上图子菜单1.1进行插入菜单项1.3.1操作
    //在程序源码的显示更新窗口
    ShowWindow(hwnd,nCmdShow);
    UpdateWindow(hwnd);
    
    //在程序源码的显示更新窗口后插入
    //插入菜单项
    AppendMenu(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),MF_ENABLED,40012,"菜单项1.1.3");
    程序运行结果:
      
  • 让上图菜单项1.1.1禁用:
     
    //在程序源码的显示更新窗口
    ShowWindow(hwnd,nCmdShow);
    UpdateWindow(hwnd);
    
    //在程序源码的显示更新窗口后插入
    //插入菜单项
    AppendMenu(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),MF_ENABLED,40012,"菜单项1.1.3");
    //禁用菜单项
    EnableMenuItem(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),0,MF_BYPOSITION | MF_GRAYED);
    运行结果:

创建弹出菜单

步骤:

  • 载入菜单资源
    (用 GetSubMenu,非 LoadMenu 或 getMenu,因为后两种获得的菜单句柄都是主菜单的句柄,而主菜单句柄不适合用 TrackPopupMenu 显示弹出菜单,若用的是主菜单句柄作为弹出菜单句柄时候效果如下图所示)
  • 调用 TrackPopupMenu 显示弹出菜单

流程图如下:

代码示例:

.rc 资源内容

View Code
/***********************************************/
//主菜单
IDR_MENU1 MENU 
BEGIN
    POPUP "菜单1"
    BEGIN
        POPUP "子菜单1.1"
        BEGIN
            MENUITEM "菜单项1.1.1",                    ID_40001
            MENUITEM "菜单项1.2.1",                    ID_40002
        END
        MENUITEM "菜单项1.2",                      ID_40003
        MENUITEM SEPARATOR
        MENUITEM "菜单项1.3",                      ID_40004
        MENUITEM "菜单项1.4",                      ID_40005
    END
    POPUP "菜单2"
    BEGIN
        MENUITEM "菜单项2.1",                      ID_40006
        MENUITEM "菜单项2.2",                      ID_40007
    END
END
/***********************************************/
//弹出菜单
IDR_MENU2 MENU 
BEGIN
    POPUP "弹出菜单"
    BEGIN
        MENUITEM "弹出菜单项1.1",                    ID_A_40012
        MENUITEM "弹出菜单项1.2",                    ID_A_40013
    END
END

加载弹出菜单

hmenuPop=GetSubMenu(LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2)),0);

显示弹出菜单

POINT p;
switch(uMsg)
{
//添加鼠标左键单击事件响应处理,即显示弹出菜单
case WM_RBUTTONDOWN:
    p.x=LOWORD(lParam); 
    p.y=HIWORD(lParam);
    //将窗口坐标转换成屏幕坐标
    ClientToScreen(hwnd,&p); 
    TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL);
    break;

.....

 程序运行结果(在鼠标右键单击后):


菜单加速键

 .rc 中的菜单格式

格式:

  • 加速键ID  ACCELERATORS
    BEGIN
    键名,命令ID [,类型] [,选项]
    …  
    END

 说明:

  • 加速键ID:  一个字符串或者是1~65535之间的数字
  • ACCELERATORS:   关键字
  • BEGIN:   关键字,表示加速键列表的开始
  • 键名:   表示加速键对应的按钮,可以有3种方式定义
    • “^字母”:表示Ctrl键加上字母键.
    • “字母”:表示字母,这时类型必须指明是VIRTKEY
    • 数值:表示ASCII码为该数值的字母,这时类型必须指明为ASCII
  • 命令ID:   按下加速键后,Windows向程序发送的命令ID.如果想把加速键和菜单项关联起来,这里就是要关联期间项的命令ID
  • 类型:   用来指定键的定义方式,可以是 VIRTKEY 和 ASCII,分别用来表示“键名”字段定义的是虚拟键还是ASCII码
  • 选项:   可以是 Alt, Control 或 Shift 中的单个或多个,如果指定多个,则中间用逗号隔开,表示加速键是按键加上这些控制键的组合键.这些选项只能在类型是VIRTKEY的情况下才能使用
  • END 关键字,表示加速键列表的结束

编写菜单资源加速键

编写步骤:

  • 加载菜单加速键资源:   LoadAccelerators
  • 修改消息循环:  (即在消息循环中先把消息派送给转换菜单加速键,然后在派送给转换消息最后分配消息,如下图所示)

代码样例(为菜单项1.4增加快捷键 Crt+Alt+K):

.rc资源:

IDR_ACCELERATOR1 ACCELERATORS 
BEGIN
    "K",            ID_40009,               VIRTKEY, CONTROL, ALT, NOINVERT
END

 加载菜单加速资源:

HACCEL haccel;
haccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1));

 更改消息循环:

while(GetMessage(&msg,NULL,0,0))
{
    if(!TranslateAccelerator(hwnd,haccel,&msg))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
}

 添加菜单项1.4 响应事件:

switch(uMsg)
{
//添加鼠标左键单击事件响应处理,即卸载对话框
case WM_RBUTTONDOWN:
    p.x=LOWORD(lParam); 
    p.y=HIWORD(lParam);
    //将窗口坐标转换成屏幕坐标
    ClientToScreen(hwnd,&p); 
    TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL);
    break;
//添加菜单响应事件
case WM_COMMAND:
    switch(LOWORD(wParam))
    {
        //添加菜单项1.4响应事件
    case ID_40009:
        MessageBox(hwnd,"success!","test",MB_OK);
        break;
    }
    break;

.....

程序源码:

View Code
#include<windows.h>
#include"resource.h"

HMENU hmenuPop;//弹出菜单句柄

LRESULT CALLBACK textprom(
    HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
);

int WINAPI WinMain(
    HINSTANCE hInstance,  // handle to current instance
    HINSTANCE hPrevInstance,  // handle to previous instance
    LPSTR lpCmdLine,      // pointer to command line
    int nCmdShow          // show state of window
)
{
    WNDCLASS wndclass;
    HWND hwnd;
    HMENU hmenu;
    MSG msg;
    HACCEL haccel;

    //设计窗口类
    wndclass.cbClsExtra=0;
    wndclass.cbWndExtra=0;
    wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndclass.hIcon=LoadIcon(NULL,IDI_ERROR);
    wndclass.hInstance=hInstance;
    wndclass.lpfnWndProc=textprom;
    wndclass.lpszClassName="text";
    wndclass.lpszMenuName=NULL;
    //wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);
    wndclass.style=CS_HREDRAW | CS_VREDRAW;

    //注册窗口类
    if(!RegisterClass(&wndclass))
        MessageBox(NULL,"create windows error!","error",MB_OK | MB_ICONSTOP);

    //创建无菜单资源的窗口窗口
    hwnd=CreateWindow("text","hellow world",WS_DLGFRAME | WS_MINIMIZEBOX | WS_SYSMENU,0,0,500,300,NULL,NULL,hInstance,NULL);

    /*
    //载入菜单资源
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    //创建有菜单资源的窗口
    hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
    CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);*/

    //载入菜单资源,并在窗口加载菜单资源
    hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1));
    SetMenu(hwnd,hmenu);

    //载入弹出菜单资源
    hmenuPop=GetSubMenu(LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2)),0);
    //hmenuPop=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2));

    //显示更新窗口
    ShowWindow(hwnd,nCmdShow);
    UpdateWindow(hwnd);

    //加载菜单加速资源
    haccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1));

    //消息循环
    while(GetMessage(&msg,NULL,0,0))
    {
        if(!TranslateAccelerator(hwnd,haccel,&msg))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    }

    return msg.wParam;
}

LRESULT CALLBACK textprom(
    HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
)
{
    POINT p;
    switch(uMsg)
    {
    //添加鼠标左键单击事件响应处理,即卸载对话框
    case WM_RBUTTONDOWN:
        p.x=LOWORD(lParam); 
        p.y=HIWORD(lParam);
        //将窗口坐标转换成屏幕坐标
        ClientToScreen(hwnd,&p); 
        TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL);
        break;
    //添加菜响应事件
    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        //添加菜单项1.4响应事件
        case ID_40009:
            MessageBox(hwnd,"success!","test",MB_OK);
            break;
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

运行结果(在单击菜单项1.4或者按下Ctrl+Alt+K的组合键时):


MFC下菜单消息路由

菜单消息路由

Cview -> CDocument -> CFrameWnd -> CWinApp

设置自己的菜单消息机制

MFC为菜单提供了一种命令更新机制,所以程序运行时,根据此机制去判断哪个菜单可以使用,哪个菜单不能使用,然后显示其相应的状态.默认情况下,所有菜单项的更新都是由MFC的命令更新机制完成的,如果我们想自己更改菜单的状态,那就必须把m_bAutoMenuEnable变量设置为false,之后我们自己对菜单项状态更新才能起作用

MFC的菜单消息机制

但是利用MFC编程时候,菜单项状态的维护依赖于CN_UPDATE_COMMAND_UI消息,MFC在后台要做的工作是:当要显示菜单时,操作系统发出WM_INITMENUPOPUP消息,然后由程序窗口基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate(),这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针.这时,系统会自动判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息.如果找到这样一个宏,就会调用相应的消息相应函数进行处理,在这个函数中,可以利用传递进来的CCmdUI对象去调用相应的函数,使该激活或禁用该菜单项.当更新王第一菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,知道完成所有菜单项的处理.

原文地址:https://www.cnblogs.com/kzang/p/2746070.html