程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Win32開發入門(6):創建右鍵菜單

Win32開發入門(6):創建右鍵菜單

編輯:關於C++

快捷菜單,說得容易理解一點,就是右鍵菜單,當我們在某個區域內單擊鼠標右鍵,會彈出一些菜 單項。這種類型的菜單,是隨處可見的,我們在桌面上右擊一下,也會彈出一個菜單。

右鍵菜 單的好處就是方便,它經常和我們正在操作的某個UI元素聯系起來,比如我們正在使用文本框輸入文本 ,我們在文本框中右擊,就會看到可能有【復制】【清空】【全選】之類的選項,所以,右鍵菜單也稱 為“上下文菜單(Context Menu)”。

一般來說,創建並使用快捷菜單,可以按照以下步驟進 行:

1、用資源編輯器創建菜單。

2、當我們在窗口上按下鼠標右鍵,當系統處理 WM_RBUTTONUP時會向我們的應用程序發送一條WM_CONTEXTMENU消息,我們通過響應這條消息來決定是否 彈出菜單。

3、計算菜單彈出的位置,一般在我們鼠標指針的右下方,該坐標是基於屏幕的,不 是窗口的。

4、調用TrackPopupMenu函數顯示快捷菜單。

5、因為這種菜單是不屬於某個 窗口的,它的內存資源不會在窗口銷毀時被回收,因此,在TrackPopupMenu返回後要調用DestroyMenu 來銷毀菜單的資源,釋放內存。

好的,基本思路有了,我們就按照這個思路來試一試,看能不 能實現一個右鍵菜單。

首先,用資源編輯器建立一個菜單,因為我們的彈出菜單一般只顯示一 系列菜項,是沒有菜單的頭部,不像菜單欄。因此,我們把菜單做成這樣:

快捷菜單只會顯示我用畫筆圈起來的那部分,而上面的【abc】是不顯示的,所以你可以讓它 空著,也可以隨便輸入一些內容。

然後為每個菜單項設置ID就行了,資源編輯器有時候會產生 一堆沒有被使用的ID宏,這些我們可以手動刪除,當然也可以不管它,反正不影響程序的編譯,因為頭 文件是不參與編譯的。我們編譯的時候只是編譯.cpp文件。

接下來就是捕捉WM_CONTEXTMENU消 息。顯示菜單。

case WM_CONTEXTMENU:     
    {     
        //加載菜單資源     
        HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), 

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;

首先用LoadMenu來加載資源文件中的菜單,注意,它加載的是整個菜單欄,而我 們要的是圖中標注的子項。

我們這裡只有一個子彈出項,所以,GetSubMenu函數獲取子項時,索引應為0。

根據 MSDN文檔的說明,WM_CONTEXTMENU消息的wParam參數指的是彈出菜單的窗口的句柄,lParam參數的低字 位是鼠標指針的水平坐標,高字位指的是垂直坐標。

但我們不用自己去轉換,我們通過 GET_X_LPARAM和GET_Y_LPARAM兩個宏可以把lParam中的值轉為坐標值,類型為int,要使用這兩個宏, 需要包含WindowsX.h頭文件。接著調用TrackPopupMenu來顯示菜單,最後銷毀菜單。

函數的具 體參數我不想抄MSDN了,大家可以上MSDN查查。如果你覺得英文文檔看得不舒服,你不妨使一下技巧, 你可以在百度百科上搜,有中文說明,還有一些VB 6 的網站也有API的中文說明,你可以參考一下。

為了使菜單點擊後程序能做出反應,我們還要捕捉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;

我們來運行一下,看看能不能起作用。

我們感覺到,程序好像是成功了,目的也似乎達到了,但是,如果你細 心研究一下,你會發現一個問題,通常我們窗口的快捷菜單都是在窗口的客戶區域右擊才出現,即除了 標題欄和邊框,但我們這個程序,你試試,在標題欄上右擊,也會出現快捷菜單,而且把系統菜單也覆 蓋掉了。

很顯然,我們是不能這樣做的,很不道德,很不忠不孝不仁不義 。所以,我們還要考慮一下,用戶鼠標右擊的位置是否在我們的客戶區域范圍內。要判斷某個點是否在 一個矩形范圍內,我們可以用PtInRect函數。

於是,把上面的代碼改成這樣:

case 

WM_CONTEXTMENU:     
    {     
        RECT rect;     
        POINT pt;     
        // 獲取鼠標右擊是的坐標     
        pt.x = GET_X_LPARAM(lParam);     
        pt.y = GET_Y_LPARAM(lParam);     
        //獲取客戶區域大小     
        GetClientRect((HWND)wParam, &rect);     
        //判斷點是否位於客戶區域內     
        if(PtInRect(&rect, pt))     
        {     
            //加載菜單資源     
            HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), 

MAKEINTRESOURCE(IDR_CONTEXT));     
            if(hroot)     
            {     
                // 獲取第一個彈出菜單     
                HMENU hpop = GetSubMenu(hroot,0);     
                //顯示快捷菜單     
                TrackPopupMenu(hpop,     
                    TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,     
                    pt.x,     
                    pt.y,     
                    0,     
                    (HWND)wParam,     
                    NULL);     
                // 用完後要銷毀菜單資源     
                DestroyMenu(hroot);     
            }     
        }     
    }     
    break;

然後再次運行,可是你會發現,靠,問題更嚴重了,無論我在窗口的哪個地方右 擊,菜單都不出來了。

代碼中是用GetClientRect函數來獲取窗口客戶區域的矩形位置的,我們 明明是在窗口中的可視區域右擊了,但為什麼會沒有看到菜單出來呢?我們在調用PtInRect的地方下一 個斷點,然後調試運行,我們來比較一下,到底鼠標右擊的坐標在不在客戶區域的矩形內。

有一點我們要注意的,GetClientRect它計算的標准是相對於窗 口的,而WM_CONTEXTMENU取出的坐標是基於屏幕的,兩個參照點不同,所以在PtInRect中無法正確地比 較。所以,我們需要調用ScreenToClient函數把屏幕坐標轉為客戶區域坐標。但是在彈出菜單的時候, 因為我們要傳入基於屏幕的坐標,所以,在顯示菜單前要用ClientToScreen來還原坐標為相對於屏幕的 點。

即:

ScreenToClient....

    ..........if  PtInRect

          ...........ClientToScreen

         ............TrackPopupMenu

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);     
            }     
        }     
    }

這樣一來,就把坐標問題解決了,現在可以彈出菜單了。但還有一個問題沒有解決,你 會發現,現在在窗口的標題欄上右擊,快捷菜單不會再出現了,但是,同時,系統菜單也沒有出現。因 為系統菜單是由系統來處理的,所以,解決這問題很簡單,只要我們把WM_CONTEXT消息發回給系統來處 理就行了。

方法一:我們判斷了如果右擊點在窗口的客戶區域時顯示菜單,那麼,如果不在這 個區域內,就把消息再傳回給系統處理。

else 
{     
    return DefWindowProc(hwnd, msg, wParam, lParam);     
}

方法二:在WindowsProc函數的最後,統一把所有消息都返回給操作系統處理。

default:     
    // 如果不處理消息,交回系統處理     
    return DefWindowProc(hwnd, msg, wParam, lParam);     
}     
return DefWindowProc(hwnd, msg, wParam, lParam);

反正目的只有一個,把 WM_CONTEXTMENU消息路由回給系統處理就行了。現在再運行一下,系統菜單可以顯示。從這一點我們可 以學到一個技巧,如果你想屏蔽窗口的系統菜單,你應該知道怎麼做了,就是不讓系統有機會響應 WM_CONTEXTMENU消息就行了。另外,按Shift + F10快捷鍵也會收到WM_CONTEXTMENU消息。

完整的代碼清單如下:

#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);     
    }     
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved