跟我一起玩Win32開發(6):創建右鍵菜單


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

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

 

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

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

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

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

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

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

 

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

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

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

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

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

[cpp]  view plain  copy
 
  1. case WM_CONTEXTMENU:  
  2.     {  
  3.         //加載菜單資源  
  4.         HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(IDR_CONTEXT));  
  5.         if(hroot)  
  6.         {  
  7.             // 獲取第一個彈出菜單  
  8.             HMENU hpop = GetSubMenu(hroot,0);  
  9.             // 獲取鼠標右擊是的坐標  
  10.             int px = GET_X_LPARAM(lParam);  
  11.             int py = GET_Y_LPARAM(lParam);  
  12.             //顯示快捷菜單  
  13.             TrackPopupMenu(hpop,  
  14.                 TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,  
  15.                 px,  
  16.                 py,  
  17.                 0,  
  18.                 (HWND)wParam,  
  19.                 NULL);  
  20.             // 用完后要銷毀菜單資源  
  21.             DestroyMenu(hroot);  
  22.         }  
  23.     }  
  24.     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消息。

[cpp]  view plain  copy
 
  1. case WM_COMMAND:  
  2.     {  
  3.         switch(LOWORD(wParam))  
  4.         {  
  5.         case IDM_WANG:  
  6.             MessageBox(hwnd,L"你選擇了王維。",L"提示",MB_OK);  
  7.             break;  
  8.         case IDM_MENG:  
  9.             MessageBox(hwnd,L"你選擇了孟浩然。",L"提示",MB_OK);  
  10.             break;  
  11.         case IDM_LI:  
  12.             MessageBox(hwnd,L"你選擇了李白。",L"提示",MB_OK);  
  13.             break;  
  14.         }  
  15.     }  
  16.     return 0;  


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

 

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

 

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

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

[cpp]  view plain  copy
 
  1. case WM_CONTEXTMENU:  
  2.     {  
  3.         RECT rect;  
  4.         POINT pt;  
  5.         // 獲取鼠標右擊是的坐標  
  6.         pt.x = GET_X_LPARAM(lParam);  
  7.         pt.y = GET_Y_LPARAM(lParam);  
  8.         //獲取客戶區域大小  
  9.         GetClientRect((HWND)wParam, &rect);  
  10.         //判斷點是否位於客戶區域內  
  11.         if(PtInRect(&rect, pt))  
  12.         {  
  13.             //加載菜單資源  
  14.             HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(IDR_CONTEXT));  
  15.             if(hroot)  
  16.             {  
  17.                 // 獲取第一個彈出菜單  
  18.                 HMENU hpop = GetSubMenu(hroot,0);  
  19.                 //顯示快捷菜單  
  20.                 TrackPopupMenu(hpop,  
  21.                     TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,  
  22.                     pt.x,  
  23.                     pt.y,  
  24.                     0,  
  25.                     (HWND)wParam,  
  26.                     NULL);  
  27.                 // 用完后要銷毀菜單資源  
  28.                 DestroyMenu(hroot);  
  29.             }  
  30.         }  
  31.     }  
  32.     break;  

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

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

 


 

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

即:

ScreenToClient....

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

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

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

 

[cpp]  view plain  copy
 
  1. case WM_CONTEXTMENU:  
  2.     {  
  3.         RECT rect;  
  4.         POINT pt;  
  5.         // 獲取鼠標右擊是的坐標  
  6.         pt.x = GET_X_LPARAM(lParam);  
  7.         pt.y = GET_Y_LPARAM(lParam);  
  8.         //獲取客戶區域大小  
  9.         GetClientRect((HWND)wParam, &rect);  
  10.         //把屏幕坐標轉為客戶區坐標  
  11.         ScreenToClient((HWND)wParam, &pt);  
  12.         //判斷點是否位於客戶區域內  
  13.         if(PtInRect(&rect, pt))  
  14.         {  
  15.             //加載菜單資源  
  16.             HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(IDR_CONTEXT));  
  17.             if(hroot)  
  18.             {  
  19.                 // 獲取第一個彈出菜單  
  20.                 HMENU hpop = GetSubMenu(hroot,0);  
  21.                 // 把客戶區坐標還原為屏幕坐標  
  22.                 ClientToScreen((HWND)wParam, &pt);  
  23.                 //顯示快捷菜單  
  24.                 TrackPopupMenu(hpop,  
  25.                     TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,  
  26.                     pt.x,  
  27.                     pt.y,  
  28.                     0,  
  29.                     (HWND)wParam,  
  30.                     NULL);  
  31.                 // 用完后要銷毀菜單資源  
  32.                 DestroyMenu(hroot);  
  33.             }  
  34.         }  
  35.     }  

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

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

[cpp]  view plain  copy
 
  1. else  
  2. {  
  3.     return DefWindowProc(hwnd, msg, wParam, lParam);  
  4. }  


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

[cpp]  view plain  copy
 
  1. default:  
  2.     // 如果不處理消息,交回系統處理  
  3.     return DefWindowProc(hwnd, msg, wParam, lParam);  
  4. }  
  5. return DefWindowProc(hwnd, msg, wParam, lParam);  


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

 

完整的代碼清單如下:

[cpp]  view plain  copy
 
  1. #include <Windows.h>  
  2. #include "resource.h"  
  3. #include <WindowsX.h>  
  4.   
  5. LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);  
  6.   
  7. int WINAPI WinMain(  
  8.     HINSTANCE hThisApp,  
  9.     HINSTANCE hPrevApp,  
  10.     LPSTR cmdLine,  
  11.     int nShow)  
  12. {  
  13.     WNDCLASS wc = { };  
  14.     wc.hbrBackground = (HBRUSH)COLOR_WINDOW;  
  15.     wc.lpszClassName = L"MyApp";  
  16.     wc.style = CS_HREDRAW | CS_VREDRAW;  
  17.     wc.hInstance = hThisApp;  
  18.     wc.lpfnWndProc = (WNDPROC)MyMainWindowProc;  
  19.     //注冊窗口類  
  20.     RegisterClass(&wc);  
  21.     //創建窗口  
  22.     HWND hwnd = CreateWindow(  
  23.         L"MyApp",  
  24.         L"我的超級應用",  
  25.         WS_OVERLAPPEDWINDOW,  
  26.         60,  
  27.         25,  
  28.         420,  
  29.         300,  
  30.         NULL,  
  31.         NULL,  
  32.         hThisApp,  
  33.         NULL);  
  34.     if(hwnd == NULL)  
  35.         return 0;  
  36.     // 顯示窗口  
  37.     ShowWindow(hwnd, nShow);  
  38.     //消息循環  
  39.     MSG msg;  
  40.     while(GetMessage(&msg, NULL, 0, 0))  
  41.     {  
  42.         TranslateMessage(&msg);  
  43.         DispatchMessage(&msg);  
  44.     }  
  45.     return 0;  
  46. }  
  47.   
  48. LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)  
  49. {  
  50.     switch(msg)  
  51.     {  
  52.     case WM_DESTROY:  
  53.         PostQuitMessage(0);  
  54.         return 0;  
  55.     case WM_COMMAND:  
  56.         {  
  57.             switch(LOWORD(wParam))  
  58.             {  
  59.             case IDM_WANG:  
  60.                 MessageBox(hwnd,L"你選擇了王維。",L"提示",MB_OK);  
  61.                 break;  
  62.             case IDM_MENG:  
  63.                 MessageBox(hwnd,L"你選擇了孟浩然。",L"提示",MB_OK);  
  64.                 break;  
  65.             case IDM_LI:  
  66.                 MessageBox(hwnd,L"你選擇了李白。",L"提示",MB_OK);  
  67.                 break;  
  68.             }  
  69.         }  
  70.         return 0;  
  71.     case WM_CONTEXTMENU:  
  72.         {  
  73.             RECT rect;  
  74.             POINT pt;  
  75.             // 獲取鼠標右擊是的坐標  
  76.             pt.x = GET_X_LPARAM(lParam);  
  77.             pt.y = GET_Y_LPARAM(lParam);  
  78.             //獲取客戶區域大小  
  79.             GetClientRect((HWND)wParam, &rect);  
  80.             //把屏幕坐標轉為客戶區坐標  
  81.             ScreenToClient((HWND)wParam, &pt);  
  82.             //判斷點是否位於客戶區域內  
  83.             if(PtInRect(&rect, pt))  
  84.             {  
  85.                 //加載菜單資源  
  86.                 HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(IDR_CONTEXT));  
  87.                 if(hroot)  
  88.                 {  
  89.                     // 獲取第一個彈出菜單  
  90.                     HMENU hpop = GetSubMenu(hroot,0);  
  91.                     // 把客戶區坐標還原為屏幕坐標  
  92.                     ClientToScreen((HWND)wParam, &pt);  
  93.                     //顯示快捷菜單  
  94.                     TrackPopupMenu(hpop,  
  95.                         TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,  
  96.                         pt.x,  
  97.                         pt.y,  
  98.                         0,  
  99.                         (HWND)wParam,  
  100.                         NULL);  
  101.                     // 用完后要銷毀菜單資源  
  102.                     DestroyMenu(hroot);  
  103.                 }  
  104.             }  
  105.             else  
  106.             {  
  107.                 return DefWindowProc(hwnd, msg, wParam, lParam);  
  108.             }  
  109.         }  
  110.         break;  
  111.     default:  
  112.         // 如果不處理消息,交回系統處理  
  113.         return DefWindowProc(hwnd, msg, wParam, lParam);  
  114.     }  
  115. }  


免責聲明!

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



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