我正在向我的視窗添加背景關系選單,我似乎發現了 Win32 的一個怪癖:因為彈出選單將滑鼠訊息轉發到其所有者視窗,而 WM_RBUTTONUP 的默認行為是發送 WM_CONTEXTMENU,如果您打開一個彈出選單在 WM_CONTEXTMENU 的處理程式中,您可以右鍵單擊選單以打開另一個選單。這將失敗,因為選單已打開。
這似乎是一個非常明顯的常見錯誤,但即使是微軟的官方 WM_CONTEXTMENU 指南也沒有提到它。也沒有任何其他教程。
那么,不嘗試在另一個選單中打開彈出選單的最干凈方法是什么?
當然,可以簡單地添加一個全域標志來指示選單已打開,但這并不干凈。也可以忽略來自 的錯誤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;
}
uj5u.com熱心網友回復:
我想TrackPopupMenu這是沒有人檢查錯誤的功能之一。畢竟,只要輸入引數有效,只有在資源不足(記憶體或 gdi/用戶句柄)時才會失敗(在正常的非遞回情況下)。如果它失敗了,MessageBox很可能也會失敗,所以你甚至不能通知用戶。
正常的編碼模式是只檢查您的選單命令 ID 并忽略其他所有內容。
TPM_RECURSE在 IE 4/5 時代添加的標志允許您將彈出選單放在彈出選單上,但從來沒有人這樣做過。
uj5u.com熱心網友回復:
從檔案
TrackPopupMenuEx
... 如果在 fuFlags 引數中指定 TPM_RETURNCMD,則回傳值是用戶選擇的專案的選單項識別符號。如果用戶沒有進行選擇就取消了選單,或者發生了錯誤,則回傳值為零。
如果彈出選單已經打開并且用戶右鍵單擊它,或者用戶取消選單,則函式回傳FALSE。無論哪種情況,您都不想處理結果,因此只需立即回傳即可。如果res非零,則對其進行處理。
BOOL res = TrackPopupMenuEx(hm,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
pt.x, pt.y, hwnd, NULL);
if (!res) return 0;
std::cout << "Menu selection: " << res << "\n";
或者您可以使用GetLastError來查找失敗的原因。是因為用戶取消,右鍵單擊選單,還是其他原因
if (!res)
{
int err = GetLastError();
if (err != 0 && err != ERROR_POPUP_ALREADY_ACTIVE)
std::cout << "Unexpected error " << err << "\n";
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/377284.html
上一篇:如何獲取視窗中標簽的句柄
