【发布时间】:2022-01-03 21:03:18
【问题描述】:
我正在向我的窗口添加一个上下文菜单,我似乎发现了 Win32 的一个怪癖:因为弹出菜单将鼠标消息转发到它的所有者窗口,而 WM_RBUTTONUP 的默认行为是发送 WM_CONTEXTMENU,如果你打开WM_CONTEXTMENU 处理程序中的弹出菜单,您可以右键单击该菜单以打开另一个菜单。这会失败,因为菜单已经打开。
这似乎是一个非常明显的常见错误,但even the official WM_CONTEXTMENU guide from Microsoft 甚至没有提及它。其他教程也没有。
那么,不尝试在另一个菜单中打开弹出菜单的最干净的方法是什么?
当然,可以简单地添加一个全局标志来指示菜单已打开,但这并不干净。也可以忽略来自TrackPopupMenuEx 的错误,但这也不干净。我无法想象 Win32(尽管如此可怕)会鼓励程序员做这样的事情。
我研究了一些常见控件的行为方式:
- 编辑控件不会将 WM_RBUTTONUP 传递给 DefWindowProc,除非它收到以前的 WM_RBUTTONDOWN。因为菜单系统在内部处理 WM_RBUTTONDOWN 而不会调度它,这意味着它不会在打开菜单时将 WM_RBUTTONUP 传递给 DefWindowProc。
- Microsoft 管理控制台中的树形视图会创建一个临时窗口来关联菜单。
- 任务管理器中的详细信息列表视图确实接收到递归 WM_CONTEXTMENU;我假设它尝试创建菜单并忽略错误。
这个测试程序演示了这个现象:
#include <Windows.h>
#include <iostream>
static LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
}
return 0;
case WM_RBUTTONDOWN:
std::cout << "got WM_RBUTTONDOWN\n";
return DefWindowProc(hwnd, msg, wParam, lParam);
case WM_RBUTTONUP:
std::cout << "got WM_RBUTTONUP\n";
return DefWindowProc(hwnd, msg, wParam, lParam);
case WM_CONTEXTMENU:
std::cout << "got WM_CONTEXTMENU\n";
{
HMENU hm = CreatePopupMenu();
AppendMenu(hm, MF_STRING, 1234, L"Hello");
AppendMenu(hm, MF_STRING, 4321, L"world");
POINT pt;
GetCursorPos(&pt);
SetLastError(0);
int res = TrackPopupMenuEx(hm, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, hwnd, NULL);
int err = GetLastError();
std::cout << "TrackPopupMenuEx returned " << res << ", error " << err << std::endl;
SetLastError(0);
}
return 0;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
int main()
{
WNDCLASSEX wcx = {0};
wcx.cbSize = sizeof wcx;
wcx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hInstance = (HINSTANCE)GetModuleHandle(NULL);
wcx.lpfnWndProc = window_proc;
wcx.lpszClassName = L"TestWindowClass";
RegisterClassEx(&wcx);
CreateWindowEx(0, wcx.lpszClassName, L"Popup menu message test", WS_VISIBLE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, wcx.hInstance, NULL);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
【问题讨论】:
-
所以当你右键单击时没有任何反应。正如预期的那样,失败是一种特征。
-
如果您提供标志
TPM_RIGHTBUTTON,那么右键将用于选择菜单项,而不是启动嵌套的WM_CONTEXTMENU消息。 -
@HansPassant 当然,在显示第二个上下文菜单时可以忽略该错误。那不是很干净。有没有替代方法?
-
菜单不应该按照您描述的方式递归,除非您通过 TPM_RECURSE 标志明确要求该行为并处理 WM_MENURBUTTONUP 消息
-
与问题无关但
GetCursorPos错误,LPARAM 告诉您正确的位置。 (鼠标可以在点击后但在收到消息之前移动)