菜單編寫(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對象就設置為與第二個菜單項相關聯,依此順序進行,知道完成所有菜單項的處理.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM