【发布时间】:2021-05-31 21:32:34
【问题描述】:
我正在使用 C++ 上的 WinAPI (win32) 编写 Windows 应用程序,但无法弄清楚 ALT 键发生了什么。我正在处理 WM_KEYDOWN 和 WM_SYSKEYDOWN,所以所有按键都应该通过。但是,虽然检测到每个 ALT 键,但每隔一个 ALT 键就会被跳过。
每当我连续获得第二个 WM_SYSKEYUP 时,我是否必须生成自己的 keydown 事件,还是我的消息处理有问题?
详情
点击这些键:ALT ALT ALT ALT ALT(alt 五次)
预期消息序列:DUDUDUDUDU(先下后上,重复五次)
观察到的消息序列:DUUDUUDU(每一秒都不见了)
点击这些键:ALT C ALT C ALT C ALT C ALT C(在两者之间添加其他按键 - 在点击 C 之前释放 ALT)
预期的 VK_MENU 消息序列:DUDUDUDUDU(先下后上,重复五次)
观察到的 VK_MENU 消息序列:DUDUDUDUDU(如预期的那样!)
似乎添加其他按键(甚至鼠标事件)会取消任何 Windows 正在执行的操作并强制它向应用发送所有 ALT 按键。
示例代码(Visual c++17):
创建一个窗口,在按下 ALT 时将“D”添加到窗口标题,在释放 ALT 时将“U”添加到窗口标题
#ifndef UNICODE
#define UNICODE
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define STRICT
#include <Windows.h>
#include <optional>
#include <malloc.h>
WCHAR* title;
int titleLen = 0;
const int maxTitleLen = 64;
void updateWindowTitle(WCHAR c, HWND hwnd)
{
if (titleLen >= maxTitleLen) return;
title[titleLen++] = c;
title[titleLen] = 0;
SetWindowTextW(hwnd, title);
}
LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_SYSKEYDOWN: //needed for keys like alt (VK_MENU)
case WM_KEYDOWN:
//process_keydown_event(wParam);
if (!(lParam & 0x40000000)) { //ignore autorepeat
if (wParam == VK_MENU) updateWindowTitle(L'D', hwnd);
}
break;
case WM_SYSKEYUP: //needed for keys like alt (VK_MENU)
case WM_KEYUP:
//process_keyup_event(wParam);
if (wParam == VK_MENU) updateWindowTitle(L'U', hwnd);
break;
//etc...
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// *********** STANDARD BORING SETUP BELOW ************
std::optional<int> processMessages() noexcept
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
// Return argument to PostQuitMessage as we are quitting
return msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return {}; //return empty optional when not quitting
}
int CALLBACK WinMain(
HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
int nShowCmd)
{
// Make global title point to valid memory
title = static_cast<WCHAR*>(malloc(sizeof(WCHAR) * maxTitleLen));
// Register window class
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.hInstance = hInst;
wc.hIcon = nullptr;
wc.hIconSm = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = L"my window class name";
RegisterClassEx(&wc);
// Create window 800x500
HWND hwnd = CreateWindowW(
wc.lpszClassName, L"",
WS_VISIBLE | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 500,
nullptr, nullptr, hInst, nullptr
);
// (check HWND is not NULL)
//main loop
while (true)
{
if (const auto errorcode = processMessages())
{
return errorcode.value();
}
}
}
【问题讨论】:
-
ALT 用于许多系统命令。使用 WM_SYSKEYDOWN 通常不是一个好主意。如果您想确定是否在您的上下文中按下了 ALT,您可以使用 GetKeyState/GetAsyncKeyState stackoverflow.com/questions/1811206/… PS:您的消息泵吃 CPU,这是官方示例:docs.microsoft.com/en-us/windows/win32/winmsg/…
-
我假设您已经确定您有正当理由覆盖默认的 SYSKEYDOWN 行为(也包括 F10)。我记得有几个关于这个的陷阱......像SDL这样的Afaik库几乎可以完成你所做的事情(它的源代码可能是一个开始寻找的好地方)。乍一看,我认为如果您不致电
DefWindowProc(即用return 0替换您的break)可能会有所帮助。因为通常 VK_MENU 将应用程序置于“系统菜单模式”,所以它会对消息做出不同的响应。请尝试一下,看看它是否有效。