• 跟我一起玩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. }  
  • 相关阅读:
    进程与线程
    前端教程大全
    vuex的五大属性和使用方法
    vue中以this.$xx的属性详解
    VUE-element-admin项目地址
    从零开始学 Web 之 Vue.js(三)Vue实例的生命周期
    从零开始学 Web 之 Vue.js(四)Vue的Ajax请求和跨域
    vue使用promise.all异步实现多个请求完成之后在执行某些操作
    react受控组件与非受控组件
    react生命周期
  • 原文地址:https://www.cnblogs.com/weekbo/p/8681899.html
Copyright © 2020-2023  润新知