首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Win32 開發入門(6):建立右鍵選單 (Context Menu)

     編輯:關於C++

右鍵選單概念與流程

右鍵選單 (Context Menu) 為浮動選單,不依附於視窗選單列。建立流程:

  1. 資源編輯器建立選單資源(僅需彈出式選單項目)
  2. 處理 WM_CONTEXTMENU 訊息
  3. 計算螢幕座標位置
  4. 呼叫 TrackPopupMenu 顯示
  5. 選單關閉後呼叫 DestroyMenu 釋放資源

資源編輯器建立選單

新增選單資源,結構為:根選單 → 彈出式選單 → 選單項目。根選單標題 (如「abc」) 不會顯示,僅作為容器。

右鍵選單資源結構

基本實作 (WM_CONTEXTMENU)


case WM_CONTEXTMENU: {
    HMENU hroot = LoadMenu(hInst, MAKEINTRESOURCE(IDR_CONTEXT));
    if (hroot) {
        HMENU hpop = GetSubMenu(hroot, 0); // 取得第一個彈出式選單
        int px = GET_X_LPARAM(lParam);
        int py = GET_Y_LPARAM(lParam);
        TrackPopupMenu(hpop, TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON,
                       px, py, 0, (HWND)wParam, NULL);
        DestroyMenu(hroot); // 釋放資源
    }
} break;

基本右鍵選單效果

問題:標題列右鍵也會彈出自訂選單

需判斷滑鼠位置是否在**用戶區** 內。使用 GetClientRect 取得用戶區矩形 (相對視窗座標),但 WM_CONTEXTMENU 的座標是**螢幕座標**,需轉換:


case WM_CONTEXTMENU: {
    RECT rect; POINT pt;
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    GetClientRect((HWND)wParam, &rect);
    ScreenToClient((HWND)wParam, &pt); // 螢幕→用戶區座標
    
    if (PtInRect(&rect, pt)) { // 在用戶區內
        HMENU hroot = LoadMenu(hInst, MAKEINTRESOURCE(IDR_CONTEXT));
        if (hroot) {
            HMENU hpop = GetSubMenu(hroot, 0);
            ClientToScreen((HWND)wParam, &pt); // 用戶區→螢幕座標 (TrackPopupMenu 需要)
            TrackPopupMenu(hpop, TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON,
                           pt.x, pt.y, 0, (HWND)wParam, NULL);
            DestroyMenu(hroot);
        }
    } else {
        return DefWindowProc(hwnd, msg, wParam, lParam); // 交給系統處理 (顯示系統選單)
    }
} break;

座標偵錯畫面

座標比較

標題列右鍵問題

修正後效果

選單項目命令處理 (WM_COMMAND)


case WM_COMMAND: {
    switch (LOWORD(wParam)) {
        case IDM_WANG: MessageBox(hwnd, L"你選擇了王維。", L"提示", MB_OK); break;
        case IDM_MENG: MessageBox(hwnd, L"你選擇了孟浩然。", L"提示", MB_OK); break;
        case IDM_LI:   MessageBox(hwnd, L"你選擇了李白。", L"提示", MB_OK); break;
    }
} return 0;

完整程式碼清單


#include <Windows.h>
#include "resource.h"
#include <WindowsX.h>

LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hThisApp, HINSTANCE hPrevApp, LPSTR cmdLine, int nShow) {
    WNDCLASS wc = { };
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = L"MyApp";
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance = hThisApp;
    wc.lpfnWndProc = (WNDPROC)MyMainWindowProc;
    RegisterClass(&wc);
    
    HWND hwnd = CreateWindow(L"MyApp", L"我的超級應用", WS_OVERLAPPEDWINDOW,
                             60, 25, 420, 300, NULL, NULL, hThisApp, NULL);
    if (hwnd == NULL) return 0;
    ShowWindow(hwnd, nShow);
    
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_DESTROY:
            PostQuitMessage(0); return 0;
        case WM_COMMAND: {
            switch (LOWORD(wParam)) {
                case IDM_WANG: MessageBox(hwnd, L"你選擇了王維。", L"提示", MB_OK); break;
                case IDM_MENG: MessageBox(hwnd, L"你選擇了孟浩然。", L"提示", MB_OK); break;
                case IDM_LI:   MessageBox(hwnd, L"你選擇了李白。", L"提示", MB_OK); break;
            }
        } return 0;
        case WM_CONTEXTMENU: {
            RECT rect; POINT pt;
            pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
            GetClientRect((HWND)wParam, &rect);
            ScreenToClient((HWND)wParam, &pt);
            if (PtInRect(&rect, pt)) {
                HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
                                       MAKEINTRESOURCE(IDR_CONTEXT));
                if (hroot) {
                    HMENU hpop = GetSubMenu(hroot, 0);
                    ClientToScreen((HWND)wParam, &pt);
                    TrackPopupMenu(hpop, TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON,
                                   pt.x, pt.y, 0, (HWND)wParam, NULL);
                    DestroyMenu(hroot);
                }
            } else {
                return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        } break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

重點整理

  • 右鍵選單資源:根選單 → 彈出式選單 → 項目
  • WM_CONTEXTMENU:wParam=視窗控制代碼,lParam=螢幕座標
  • 座標轉換關鍵:ScreenToClient 判斷用戶區 → ClientToScreen 還原給 TrackPopupMenu
  • 非用戶區右鍵需呼叫 DefWindowProc 讓系統顯示標題列選單
  • TrackPopupMenu 後必須 DestroyMenu 釋放資源
  • Shift+F10 也會觸發 WM_CONTEXTMENU
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved