Windows 10控制台TUI程序无法捕获鼠标滚轮事件的问题求助
解决Windows 10控制台TUI程序无法捕获鼠标滚轮事件的问题
我之前开发控制台TUI程序时也碰到过一模一样的状况——鼠标点击、移动都能正常捕获,唯独滚轮事件没反应,哪怕系统本身能滚动控制台内容。其实问题出在控制台输入模式的配置上,你漏加了一个关键的标志位!
问题原因
默认情况下,Windows控制台会把鼠标滚轮事件用来滚动窗口的缓冲区内容,不会把它传递给你的程序。要让程序能接收滚轮事件,必须在调用SetConsoleMode时加上ENABLE_EXTENDED_FLAGS这个标志,它会改变控制台的输入处理逻辑,将滚轮事件转发给你的应用。
修改后的关键代码
只需要修改main函数里设置输入模式的那一行,添加ENABLE_EXTENDED_FLAGS:
// 启用窗口、鼠标输入事件,以及扩展标志以接收滚轮事件 fdwMode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS;
另外,你还可以在MouseEventProc里添加滚动方向的判断,利用dwButtonState的高字来获取滚轮的增量(标准滚动增量是WHEEL_DELTA = 120,正数表示向上滚动,负数表示向下滚动):
case MOUSE_WHEELED: { int wheelDelta = HIWORD(mer.dwButtonState); if (wheelDelta > 0) printf("vertical mouse wheel: up\n"); else printf("vertical mouse wheel: down\n"); break; }
完整验证后的代码
这里是修改后的完整代码,测试后滚轮事件可以正常触发:
#include <windows.h> #include <stdio.h> HANDLE hStdin; DWORD fdwSaveOldMode; VOID ErrorExit(LPCSTR); VOID KeyEventProc(KEY_EVENT_RECORD); VOID MouseEventProc(MOUSE_EVENT_RECORD); VOID ResizeEventProc(WINDOW_BUFFER_SIZE_RECORD); int main(VOID) { DWORD cNumRead, fdwMode, i; INPUT_RECORD irInBuf[128]; int counter=0; // 获取标准输入句柄 hStdin = GetStdHandle(STD_INPUT_HANDLE); if (hStdin == INVALID_HANDLE_VALUE) ErrorExit("GetStdHandle"); // 保存当前输入模式,以便退出时恢复 if (! GetConsoleMode(hStdin, &fdwSaveOldMode) ) ErrorExit("GetConsoleMode"); // 启用窗口、鼠标输入事件,以及扩展标志以接收滚轮事件 fdwMode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS; if (! SetConsoleMode(hStdin, fdwMode) ) ErrorExit("SetConsoleMode"); // 循环读取并处理接下来的100个输入事件 while (counter++ <= 100) { // 等待事件 if (! ReadConsoleInput( hStdin, // 输入缓冲区句柄 irInBuf, // 读取目标缓冲区 128, // 读取缓冲区大小 &cNumRead) ) // 已读取的记录数 ErrorExit("ReadConsoleInput"); // 将事件分发至对应的处理函数 for (i = 0; i < cNumRead; i++) { switch(irInBuf[i].EventType) { case KEY_EVENT: // 键盘输入 KeyEventProc(irInBuf[i].Event.KeyEvent); break; case MOUSE_EVENT: // 鼠标输入 MouseEventProc(irInBuf[i].Event.MouseEvent); break; case WINDOW_BUFFER_SIZE_EVENT: // 屏幕缓冲区调整 ResizeEventProc( irInBuf[i].Event.WindowBufferSizeEvent ); break; case FOCUS_EVENT: // 忽略焦点事件 case MENU_EVENT: // 忽略菜单事件 break; default: ErrorExit("Unknown event type"); break; } } } // 退出时恢复输入模式 SetConsoleMode(hStdin, fdwSaveOldMode); return 0; } VOID ErrorExit (LPCSTR lpszMessage) { fprintf(stderr, "%s\n", lpszMessage); // 退出时恢复输入模式 SetConsoleMode(hStdin, fdwSaveOldMode); ExitProcess(0); } VOID KeyEventProc(KEY_EVENT_RECORD ker) { printf("Key event: "); if(ker.bKeyDown) printf("key pressed\n"); else printf("key released\n"); } VOID MouseEventProc(MOUSE_EVENT_RECORD mer) { #ifndef MOUSE_HWHEELED #define MOUSE_HWHEELED 0x0008 #endif printf("Mouse event: "); switch(mer.dwEventFlags) { case 0: if(mer.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED) { printf("left button press \n"); } else if(mer.dwButtonState == RIGHTMOST_BUTTON_PRESSED) { printf("right button press \n"); } else { printf("button press\n"); } break; case DOUBLE_CLICK: printf("double click\n"); break; case MOUSE_HWHEELED: printf("horizontal mouse wheel\n"); break; case MOUSE_MOVED: printf("mouse moved\n"); break; case MOUSE_WHEELED: { int wheelDelta = HIWORD(mer.dwButtonState); if (wheelDelta > 0) printf("vertical mouse wheel: up\n"); else printf("vertical mouse wheel: down\n"); break; } default: printf("unknown\n"); break; } } VOID ResizeEventProc(WINDOW_BUFFER_SIZE_RECORD wbsr) { printf("Resize event\n"); printf("Console screen buffer is %d columns by %d rows.\n", wbsr.dwSize.X, wbsr.dwSize.Y); }
注意事项
- 加上
ENABLE_EXTENDED_FLAGS后,控制台原本的滚轮滚动窗口功能会被禁用,滚轮事件完全由你的程序处理。 - 如果需要同时保留系统的滚动功能和程序捕获事件,这是做不到的——只能二选一,因为系统会优先处理滚轮事件,除非你通过这个标志把它接管过来。
内容的提问来源于stack exchange,提问作者Itsbananas




