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

右键菜单的好处就是方便,它经常和我们正在操作的某个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);
}
}

跟我一起玩Win32开发(6):创建右键菜单相关推荐

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

    跟我一起玩Win32开发(4):创建菜单 也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长, ...

  2. 跟我一起玩Win32开发

    跟我一起玩Win32开发(1):关于C++的几个要点 我不知道各位,一提起 C++ ,第一感觉是什么?而据俺的观察,许多人几乎成了 " 谈 C 色变 " .不管是 C 还是 C++ ...

  3. 跟我一起玩Win32开发(5):具有单选标记的菜单

    跟我一起玩Win32开发(5):具有单选标记的菜单 帅哥们,美女们,下午好,我又来误人子弟,请做好准备. 今天,我们的目的是,想要实现下图中的这种菜单效果. 就是一种类似单选按钮的菜单,多个菜单项中, ...

  4. 跟我一起玩Win32开发(25):监视剪贴板

    跟我一起玩Win32开发(25):监视剪贴板 自从郭大侠和蓉儿离开桃花岛后,最近岛比较寂静,有一种"门前冷落鞍马稀"的感觉.于是,老邪就拿出<九阴真经>认真阅读,同时用 ...

  5. CAD二次开发C#.Net编程-CAD创建右键菜单

    CAD二次开发&C#.Net编程-CAD创建右键菜单 private static ContextMenuExtension m_ContextMenu;//装载右键菜单public stat ...

  6. 基于C#的AE二次开发之图层右键菜单打开属性表及图层相关操作

    基于C#的AE二次开发之图层右键菜单打开属性表及图层相关操作 我的开发环境为ArcGIS Engine 10.2与Visual studio2010.主地图名称为axMapControl1,Toc目录 ...

  7. qt创建右键菜单,显示在鼠标点击处

    引言 给窗口创建右键菜单,右键的时候,右键菜单出现在鼠标点击处.同时设置右键菜单的样式,右键菜单为圆角,起初设置样式的时候,右键菜单的背景在圆角边缘会出现黑色的方角,不能很好的显示为圆角,后来设置了右 ...

  8. 【微信易信公众平台开发】创建自定义菜单

    本系列文章均为A2BGeek原创,转载务必在明显处注明: 转载自A2BGeek的[微信易信公众平台开发]系列,原文链接:http://blog.csdn.net/a2bgeek/article/det ...

  9. cad自定义菜单cui_AutoCAD.NET二次开发:创建自定义菜单

    1.在项目中添加引用:AcCui.dll,这个DLL存放在CAD的安装目录中.但是,好像CAD2007之前的版本里没有这个DLL),我电脑上安装了CAD2004.CAD2005.CAD2008.CAD ...

最新文章

  1. eclipse中的git安装与使用
  2. call_user_func() expects parameter 1 to be a valid callback, cannot access private method
  3. Verilog 编写规范
  4. PCL:解决PCL和OpenCV冲突的方法
  5. Linux的前世今生
  6. 刚刚出炉的Asp.net网站部署视频教程
  7. MySQL中创建用户
  8. CSDN 在线峰会——揭晓阿里云核心技术竞争力
  9. 多变量微积分笔记5——梯度与方向导数
  10. 腾讯2020校园招聘笔试
  11. 毕业设计c语言课程设计,毕业设计—c语言—课程设计.doc
  12. 2018计算机中文期刊影响因子排名,2018期刊影响因子排名(1)
  13. JavaScript高级程序设计读书笔记(第6章面向对象的程序设计之创建对象)
  14. 北航计算机学院院长 孙,孙磊磊-北京航空航天大学计算机学院
  15. windows10 基于Spin的模型检测
  16. PMP章节练习—项目经理的角色
  17. Matlab中 intlinprog函数用法简介
  18. bailianoj 4124海贼王之伟大航路
  19. 数据分析行业薪资的秘密,你想知道的都在这里(1)
  20. 使用MOB免费短信验证码进行登录(注册)

热门文章

  1. 好的东西是什么东西是
  2. java计算机毕业设计新冠疫苗接种预约系统登录源码+mysql数据库+lw文档+系统+调试部署
  3. 【Kotlin学习之旅】解决错误:kotlin.NotImplementedError: An operation is not implemented: Not yet implemented
  4. python抢购火车票源代码_Python动刷新抢12306火车票的代码(附源码)
  5. mysql中in的用法详解
  6. html未知错误,未知错误是什么意思
  7. IT老兵不死:柳传志隐退留下三大愿景
  8. 小公司好还是大公司好?告诉你:小…
  9. 物联网平台应用进入深水区,服务商呈现四大竞争格局
  10. 新年快乐,Java高级架构给大家拜年了!!