快捷菜单,说得容易理解一点,就是右键菜单,当我们在某个区域内单击鼠标右键,会弹出一些菜单项。这种类型的菜单,是随处可见的,我们在桌面上右击一下,也会弹出一个菜单。

右键菜单的好处就是方便,它经常和我们正在操作的某个UI元素联系起来,比如我们正在使用文本框输入文本,我们在文本框中右击,就会看到可能有【复制】【清空】【全选】之类的选项,所以,右键菜单也称为“上下文菜单(Context Menu)”。

一般来说,创建并使用快捷菜单,可以按照以下步骤进行:

1、用资源编辑器创建菜单。

2、当我们在窗口上按下鼠标右键,当系统处理WM_RBUTTONUP时会向我们的应用程序发送一条WM_CONTEXTMENU消息,我们通过响应这条消息来决定是否弹出菜单。

3、计算菜单弹出的位置,一般在我们鼠标指针的右下方,该坐标是基于屏幕的,不是窗口的。

4、调用TrackPopupMenu函数显示快捷菜单。

5、因为这种菜单是不属于某个窗口的,它的内存资源不会在窗口销毁时被回收,因此,在TrackPopupMenu返回后要调用DestroyMenu来销毁菜单的资源,释放内存。

好的,基本思路有了,我们就按照这个思路来试一试,看能不能实现一个右键菜单。

首先,用资源编辑器建立一个菜单,因为我们的弹出菜单一般只显示一系列菜项,是没有菜单的头部,不像菜单栏。因此,我们把菜单做成这样:

快捷菜单只会显示我用画笔圈起来的那部分,而上面的【abc】是不显示的,所以你可以让它空着,也可以随便输入一些内容。

然后为每个菜单项设置ID就行了,资源编辑器有时候会产生一堆没有被使用的ID宏,这些我们可以手动删除,当然也可以不管它,反正不影响程序的编译,因为头文件是不参与编译的。我们编译的时候只是编译.cpp文件。

接下来就是捕捉WM_CONTEXTMENU消息。显示菜单。

  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消息。

  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函数。

于是,把上面的代码改成这样:

  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

  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消息发回给系统来处理就行了。

方法一:我们判断了如果右击点在窗口的客户区域时显示菜单,那么,如果不在这个区域内,就把消息再传回给系统处理。

  1. else
  2. {
  3. return DefWindowProc(hwnd, msg, wParam, lParam);
  4. }

方法二:在WindowsProc函数的最后,统一把所有消息都返回给操作系统处理。

  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消息。

完整的代码清单如下:

  1. #include <Windows.h>
  2. #include "resource.h"
  3. #include <WindowsX.h>
  4. LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
  5. int WINAPI WinMain(
  6. HINSTANCE hThisApp,
  7. HINSTANCE hPrevApp,
  8. LPSTR cmdLine,
  9. int nShow)
  10. {
  11. WNDCLASS wc = { };
  12. wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
  13. wc.lpszClassName = L"MyApp";
  14. wc.style = CS_HREDRAW | CS_VREDRAW;
  15. wc.hInstance = hThisApp;
  16. wc.lpfnWndProc = (WNDPROC)MyMainWindowProc;
  17. //注册窗口类
  18. RegisterClass(&wc);
  19. //创建窗口
  20. HWND hwnd = CreateWindow(
  21. L"MyApp",
  22. L"我的超级应用",
  23. WS_OVERLAPPEDWINDOW,
  24. 60,
  25. 25,
  26. 420,
  27. 300,
  28. NULL,
  29. NULL,
  30. hThisApp,
  31. NULL);
  32. if(hwnd == NULL)
  33. return 0;
  34. // 显示窗口
  35. ShowWindow(hwnd, nShow);
  36. //消息循环
  37. MSG msg;
  38. while(GetMessage(&msg, NULL, 0, 0))
  39. {
  40. TranslateMessage(&msg);
  41. DispatchMessage(&msg);
  42. }
  43. return 0;
  44. }
  45. LRESULT CALLBACK MyMainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  46. {
  47. switch(msg)
  48. {
  49. case WM_DESTROY:
  50. PostQuitMessage(0);
  51. return 0;
  52. case WM_COMMAND:
  53. {
  54. switch(LOWORD(wParam))
  55. {
  56. case IDM_WANG:
  57. MessageBox(hwnd,L"你选择了王维。",L"提示",MB_OK);
  58. break;
  59. case IDM_MENG:
  60. MessageBox(hwnd,L"你选择了孟浩然。",L"提示",MB_OK);
  61. break;
  62. case IDM_LI:
  63. MessageBox(hwnd,L"你选择了李白。",L"提示",MB_OK);
  64. break;
  65. }
  66. }
  67. return 0;
  68. case WM_CONTEXTMENU:
  69. {
  70. RECT rect;
  71. POINT pt;
  72. // 获取鼠标右击是的坐标
  73. pt.x = GET_X_LPARAM(lParam);
  74. pt.y = GET_Y_LPARAM(lParam);
  75. //获取客户区域大小
  76. GetClientRect((HWND)wParam, &rect);
  77. //把屏幕坐标转为客户区坐标
  78. ScreenToClient((HWND)wParam, &pt);
  79. //判断点是否位于客户区域内
  80. if(PtInRect(&rect, pt))
  81. {
  82. //加载菜单资源
  83. HMENU hroot = LoadMenu((HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(IDR_CONTEXT));
  84. if(hroot)
  85. {
  86. // 获取第一个弹出菜单
  87. HMENU hpop = GetSubMenu(hroot,0);
  88. // 把客户区坐标还原为屏幕坐标
  89. ClientToScreen((HWND)wParam, &pt);
  90. //显示快捷菜单
  91. TrackPopupMenu(hpop,
  92. TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
  93. pt.x,
  94. pt.y,
  95. 0,
  96. (HWND)wParam,
  97. NULL);
  98. // 用完后要销毁菜单资源
  99. DestroyMenu(hroot);
  100. }
  101. }
  102. else
  103. {
  104. return DefWindowProc(hwnd, msg, wParam, lParam);
  105. }
  106. }
  107. break;
  108. default:
  109. // 如果不处理消息,交回系统处理
  110. return DefWindowProc(hwnd, msg, wParam, lParam);
  111. }
  112. }

跟我一起玩Win32开发(6):创建右键菜单的更多相关文章

  1. 跟我一起玩Win32开发(转自CSDN-东邪独孤)

    跟我一起玩Win32开发(1):关于C++的几个要点 跟我一起玩Win32开发(2):完整的开发流程 跟我一起玩Win32开发(3):窗口的重绘 跟我一起玩Win32开发(4):创建菜单 跟我一起玩W ...

  2. 跟我一起玩Win32开发(17):启动和结束进程

    这里我再次说明一下,我不知道为什么,现在的人那么喜欢走极端,估计是价值观都“升级”了的缘故吧. 我撰写这一系列Win32相关的文章,并不是叫大家一定要用Win32去开发项目,仅仅是给大家了解一下,Wi ...

  3. 跟我一起玩Win32开发(4):创建菜单

    也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长,唯一厉害的一点就是不相信权威,鄙视砖家,所 ...

  4. 跟我一起玩Win32开发(18):使用对话框的两个技巧

    相信大家知道对话框怎么用了,就是先用“资源编辑器”设计一个对话框,然后在代码中加载处理.今天,我向大家分享两个使用对话框的技巧,还是比较实用的.不用担心,先喝杯茶,很简单的,一点也不复杂,总之,看俺写 ...

  5. AutoCAD.NET二次开发:创建自定义菜单(AcCui)

    从CAD2007之后,Autodesk提供了一个新的程序集AcCui.dll,使用这个程序集,我们可以方便地做一些界面方面的操作,比如创建自定义菜单. 下面介绍一下菜单的创建过程: 1.在项目中添加引 ...

  6. AutoCAD.NET二次开发:创建自定义菜单的两种方法比较

    目前我已经掌握的创建CAD菜单方法有两种: COM方式: http://www.cnblogs.com/bomb12138/p/3607929.html CUI方式: http://www.cnblo ...

  7. AutoCAD.NET二次开发:创建自定义菜单(COM)

    当我们要在CAD中创建自定菜单时,可以引用COM组件来实现. 下面是实现方式: 1.新建类库项目,并引用CAD目录(我这里用的是CAD2008)下的acdbmgd.dll.acmgd.dll,并将引用 ...

  8. Android开发 ---代码创建选项菜单、隐藏菜单项、菜单的生命周期,菜单按钮图标设置、搜索框、xml中设置子菜单

    1.activity_main.xml 描述: 定义了一个按钮 <?xml version="1.0" encoding="utf-8"?> < ...

  9. NX二次开发-自定义添加右键菜单RegisterConfigureContextMenuCallback

    首先声明这个知识我以前不知道,是夏天的时候看到别人在唐工的QQ群里问的,唐工说西门子官方有这个例子.那个时候我因为在忙其他事情,也就没去研究那个右键菜单到底是怎么做的.关于自定义添加右键菜单Regis ...

随机推荐

  1. liberOJ#6006. 「网络流 24 题」试题库 网络流, 输出方案

    #6006. 「网络流 24 题」试题库     题目描述 假设一个试题库中有 n nn 道试题.每道试题都标明了所属类别.同一道题可能有多个类别属性.现要从题库中抽取 m mm 道题组成试卷.并要求 ...

  2. O4编译 在 PingCAP 的一些技术挑战

    在 PingCAP 的一些技术挑战 http://www.zenlife.tk/challenge-at-pingcap.md 在 PingCAP 的一些技术挑战 2018-06-02 事务优化 AC ...

  3. MessageBox_ swt

    SWT有不同类型的对话框.有些对话框具有特殊的属性. MessageBox messageBox = new MessageBox(shell, SWT.OK|SWT.CANCEL); if (mes ...

  4. FZU2150 Fire Game —— BFS

    题目链接:https://vjudge.net/problem/FZU-2150 Problem 2150 Fire Game Accept: 2702    Submit: 9240 Time Li ...

  5. xunit inlinedata classdata memberdata

    https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdat ...

  6. 多态、抽象类、接口、区别(java基础知识九)

    1.多态的概述以及代码体现 * A:多态概述 * 事物存在的多种形态 * B:多态前提 * a:要有继承关系. * 一个类是父类,一个类是子类 * b:要有方法重写. * c:要有父类引用指向子类对象 ...

  7. linux初级学习笔记七:linux用户管理,密码和组命令详解!(视频序号:04_1)

    本节学习的命令: 用户管理命令:useradd,userdel,id,finger,usermod,chsh,chfn,passwd,pwck, 组管理命令:groupadd,groupmod,gro ...

  8. [SDOI2016] 模式字符串 (BZOJ4598 & VIJOS1995)

    首先直接点分+hash就可以做,每个点用hash判断是否为S重复若干次后的前缀或后缀,每个子树与之前的结果O(m)暴力合并.在子树大小<m时停止分治,则总复杂度为O(nlog(n/m)). 问题 ...

  9. 「LuoguP1238」 走迷宫

    Description 有一个m*n格的迷宫(表示有m行.n列),其中有可走的也有不可走的,如果用1表示可以走,0表示不可以走,文件读入这m*n个数据和起始点.结束点(起始点和结束点都是用两个数据来描 ...

  10. [SDOI 2008] 洞穴勘测

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=2049 [算法] LCT动态维护森林连通性 时间复杂度 : O(NlogN ^ 2) ...