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

右键菜单的好处就是方便,它经常和我们正在操作的某个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. find the longest of the shortest (hdu 1595 SPFA+枚举)

    find the longest of the shortest Time Limit: 1000/5000 MS (Java/Others)    Memory Limit: 32768/32768 ...

  2. JSON-RPC 2.0规范 翻译 中文版

    JSON-RPC 2.0规范 起源日期: 2010-03-26(基于2009-05-24的版本号) 修正: 2013-01-04 作者: JSON-RPC 工作组 <json-rpc@googl ...

  3. sanic官方文档解析之streaming(流动,滚动)和class_based_views(CBV的写法)

    1,streaming(流媒体) 1.1请求流媒体 Sanic允许你通过流媒体携带请求数据,如下,当请求结束await request.stream.read()就会返回None,仅仅只有post请求 ...

  4. redis中键值对中值的各种类型

    1 value的最基本的数据类型是String 2 如果value是一张图片 先对图片进行base64编码成一个字符串,然后再保存到redis中,用的时候进行base64解码即可. 这是base64的 ...

  5. Android开发环境搭建时遇到问题的解决方法

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/linux_loajie/article/details/33823637 Android开发环境搭建 ...

  6. spring、spring MVC、spring Boot

    Spring 是一个“引擎” Spring MVC 是基于 Spring 的一个 MVC 框架 Spring Boot 是基于 Spring4 的条件注册的一套快速开发整合包 Spring 最初利用“ ...

  7. hdu 2112 HDU Today 解题报告

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2112 题目意思:又是求最短路的,不过结合埋字符串来考查. 受之前1004 Let the Balloo ...

  8. codeforces B. Online Meeting 解题报告

    题目链接:http://codeforces.com/problemset/problem/420/B 题目意思:给出一段连续的消息记录:记录着哪些人上线或者下线.问通过给出的序列,找出可能为lead ...

  9. hdu 3746 Cyclic Nacklace(next数组求最小循环节)

    题意:给出一串字符串,可以在字符串的开头的结尾添加字符,求添加最少的字符,使这个字符串是循环的(例如:abcab 在结尾添加1个c变为 abcabc 既可). 思路:求出最小循环节,看总长能不能整除. ...

  10. perl字符集处理

    本文内容适用于perl 5.8及其以上版本. perl internal form 在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种u ...