在Windows C++ Win32控制台程序中检测Shift+箭头键组合
问题描述
我找到一个用C编写的旧VT100终端编辑器,新版Windows Terminal支持VT100转义码,所以想把它改成Windows Terminal控制台程序。移植到Windows平台C环境时遇到了问题:如何检测“Shift+箭头键”和“Shift+Ctrl+箭头键”这类按键组合? 使用getch()似乎无法检测到Shift键与箭头键的组合。
以下是当前检测按键的实现代码:
按键定义常量
#define KEY_BACKSPACE 0x101 #define KEY_ESC 0x102 #define KEY_INS 0x103 #define KEY_DEL 0x104 #define KEY_LEFT 0x105 #define KEY_RIGHT 0x106 #define KEY_UP 0x107 #define KEY_DOWN 0x108 #define KEY_HOME 0x109 #define KEY_END 0x10A #define KEY_ENTER 0x10B #define KEY_TAB 0x10C #define KEY_PGUP 0x10D #define KEY_PGDN 0x10E #define KEY_CTRL_LEFT 0x10F #define KEY_CTRL_RIGHT 0x110 #define KEY_CTRL_UP 0x111 #define KEY_CTRL_DOWN 0x112 #define KEY_CTRL_HOME 0x113 #define KEY_CTRL_END 0x114 #define KEY_CTRL_TAB 0x115 #define KEY_SHIFT_LEFT 0x116 #define KEY_SHIFT_RIGHT 0x117 #define KEY_SHIFT_UP 0x118 #define KEY_SHIFT_DOWN 0x119 #define KEY_SHIFT_PGUP 0x11A #define KEY_SHIFT_PGDN 0x11B #define KEY_SHIFT_HOME 0x11C #define KEY_SHIFT_END 0x11D #define KEY_SHIFT_TAB 0x11E #define KEY_SHIFT_CTRL_LEFT 0x11F #define KEY_SHIFT_CTRL_RIGHT 0x120 #define KEY_SHIFT_CTRL_UP 0x121 #define KEY_SHIFT_CTRL_DOWN 0x122 #define KEY_SHIFT_CTRL_HOME 0x123 #define KEY_SHIFT_CTRL_END 0x124 #define KEY_F1 0x125 #define KEY_F3 0x126 #define KEY_F5 0x127 #define KEY_ALT_LEFT 0x128 #define KEY_ALT_RIGHT 0x129 #define KEY_ALT_UP 0x130 #define KEY_ALT_DOWN 0x131 #define KEY_ALT_HOME 0x132 #define KEY_ALT_END 0x133 #define KEY_ALT_TAB 0x134 #define KEY_ALT_PGUP 0x135 #define KEY_ALT_PGDN 0x136 #define KEY_UNKNOWN 0xFFF
按键检测函数
#if defined(WIN32) || defined(_WIN64) #include "windows.h" #include "conio.h" #endif int getkey_win32() { int c1; int c2; c1 = (int)getch(); // unmapable How to detect Shift + Arrow Key?? //#define KEY_SHIFT_LEFT 0x116 //#define KEY_SHIFT_RIGHT 0x117 //#define KEY_SHIFT_UP 0x118 //#define KEY_SHIFT_DOWN 0x119 //#define KEY_SHIFT_PGUP 0x11A //#define KEY_SHIFT_PGDN 0x11B //#define KEY_SHIFT_HOME 0x11C //#define KEY_SHIFT_END 0x11D //#define KEY_SHIFT_CTRL_LEFT 0x11F //#define KEY_SHIFT_CTRL_RIGHT 0x120 //#define KEY_SHIFT_CTRL_UP 0x121 //#define KEY_SHIFT_CTRL_DOWN 0x122 //#define KEY_SHIFT_CTRL_HOME 0x123 //#define KEY_SHIFT_CTRL_END 0x124 if (c1 == 0) { c2 = (int)getch(); switch(c2) { case 0x9b: return KEY_ALT_LEFT; case 0x9d: return KEY_ALT_RIGHT; case 0x98: return KEY_ALT_UP; case 0xa0: return KEY_ALT_DOWN; case 0x99: return KEY_ALT_PGUP; case 0xa1: return KEY_ALT_PGDN; case 0x97: return KEY_ALT_HOME; case 0x9F: return KEY_ALT_END; case 0x3b: return KEY_F1; case 0x3d: return KEY_F3; case 0x3f: return KEY_F5; default: return KEY_UNKNOWN; } } else if (c1 == 0xe0) { c2 = (int)getch(); switch(c2) { case 0x73: return KEY_CTRL_LEFT; case 0x74: return KEY_CTRL_RIGHT; case 0x8d: return KEY_CTRL_UP; case 0x91: return KEY_CTRL_DOWN; case 0x77: return KEY_CTRL_HOME; case 0x75: return KEY_CTRL_END; case 0x52: return KEY_INS; case 0x53: return KEY_DEL; case 0x4b: return KEY_LEFT; case 0x4d: return KEY_RIGHT; case 0x48: return KEY_UP; case 0x50: return KEY_DOWN; case 0x47: return KEY_HOME; case 0x4f: return KEY_END; case 0x49: return KEY_PGUP; case 0x51: return KEY_PGDN; default: return KEY_UNKNOWN; } } else { switch(c1) { case 0x08: return KEY_BACKSPACE; case 0x1b: return KEY_ESC; case 0x0d: return KEY_ENTER; case 0x09: return KEY_TAB; default: return c1; } } }
解决方案
在Windows平台下,getch()无法直接识别Shift/Control与箭头键的组合,因为这类组合会生成不同的扫描码序列。可以通过以下两种方式解决:
方法一:读取完整的VT100转义序列(适配Windows Terminal)
Windows Terminal原生支持VT100转义码,按下Shift+箭头或Ctrl+Shift+箭头时会输出标准ANSI转义序列,比如:
Shift+Left:ESC[1;2DShift+Right:ESC[1;2CCtrl+Shift+Left:ESC[1;6DCtrl+Shift+Right:ESC[1;6C
修改getkey_win32()函数,先检测ESC键,再读取后续字符解析完整序列:
int getkey_win32() { int c1 = getch(); // 处理VT100转义序列(Windows Terminal专属) if (c1 == 0x1b) { // 捕获ESC键 int c2 = getch(); if (c2 == '[') { // 转义序列起始标记 int c3 = getch(); // 解析带修饰键的组合键 if (c3 == '1') { int c4 = getch(); if (c4 == ';') { int mod_code = getch() - '0'; // 2=Shift, 6=Ctrl+Shift int c5 = getch(); switch(c5) { case 'D': return (mod_code == 2) ? KEY_SHIFT_LEFT : KEY_SHIFT_CTRL_LEFT; case 'C': return (mod_code == 2) ? KEY_SHIFT_RIGHT : KEY_SHIFT_CTRL_RIGHT; case 'A': return (mod_code == 2) ? KEY_SHIFT_UP : KEY_SHIFT_CTRL_UP; case 'B': return (mod_code == 2) ? KEY_SHIFT_DOWN : KEY_SHIFT_CTRL_DOWN; case 'H': return (mod_code == 2) ? KEY_SHIFT_HOME : KEY_SHIFT_CTRL_HOME; case 'F': return (mod_code == 2) ? KEY_SHIFT_END : KEY_SHIFT_CTRL_END; } } } // 兼容普通箭头键的转义序列 switch(c3) { case 'D': return KEY_LEFT; case 'C': return KEY_RIGHT; case 'A': return KEY_UP; case 'B': return KEY_DOWN; case 'H': return KEY_HOME; case 'F': return KEY_END; } } return KEY_ESC; } // 保留原有0和0xe0序列的处理逻辑,兼容传统控制台 else if (c1 == 0) { int c2 = getch(); switch(c2) { case 0x9b: return KEY_ALT_LEFT; case 0x9d: return KEY_ALT_RIGHT; case 0x98: return KEY_ALT_UP; case 0xa0: return KEY_ALT_DOWN; case 0x99: return KEY_ALT_PGUP; case 0xa1: return KEY_ALT_PGDN; case 0x97: return KEY_ALT_HOME; case 0x9F: return KEY_ALT_END; case 0x3b: return KEY_F1; case 0x3d: return KEY_F3; case 0x3f: return KEY_F5; default: return KEY_UNKNOWN; } } else if (c1 == 0xe0) { int c2 = getch(); switch(c2) { case 0x73: return KEY_CTRL_LEFT; case 0x74: return KEY_CTRL_RIGHT; case 0x8d: return KEY_CTRL_UP; case 0x91: return KEY_CTRL_DOWN; case 0x77: return KEY_CTRL_HOME; case 0x75: return KEY_CTRL_END; case 0x52: return KEY_INS; case 0x53: return KEY_DEL; case 0x4b: return KEY_LEFT; case 0x4d: return KEY_RIGHT; case 0x48: return KEY_UP; case 0x50: return KEY_DOWN; case 0x47: return KEY_HOME; case 0x4f: return KEY_END; case 0x49: return KEY_PGUP; case 0x51: return KEY_PGDN; default: return KEY_UNKNOWN; } } else { switch(c1) { case 0x08: return KEY_BACKSPACE; case 0x0d: return KEY_ENTER; case 0x09: return KEY_TAB; default: return c1; } } }
方法二:用Windows API检测修饰键状态(兼容传统控制台)
如果需要兼容传统Windows控制台,可在读取按键时调用GetKeyState()实时检测Shift和Ctrl键的按下状态:
int getkey_win32() { int c1 = getch(); // 检测Shift和Ctrl键的按下状态 BOOL shift_pressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; BOOL ctrl_pressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; if (c1 == 0xe0) { int c2 = getch(); // 根据修饰键状态返回对应键值 switch(c2) { case 0x4b: // Left arrow if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_LEFT; else if (shift_pressed) return KEY_SHIFT_LEFT; else if (ctrl_pressed) return KEY_CTRL_LEFT; else return KEY_LEFT; case 0x4d: // Right arrow if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_RIGHT; else if (shift_pressed) return KEY_SHIFT_RIGHT; else if (ctrl_pressed) return KEY_CTRL_RIGHT; else return KEY_RIGHT; case 0x48: // Up arrow if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_UP; else if (shift_pressed) return KEY_SHIFT_UP; else if (ctrl_pressed) return KEY_CTRL_UP; else return KEY_UP; case 0x50: // Down arrow if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_DOWN; else if (shift_pressed) return KEY_SHIFT_DOWN; else if (ctrl_pressed) return KEY_CTRL_DOWN; else return KEY_DOWN; case 0x47: // Home if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_HOME; else if (shift_pressed) return KEY_SHIFT_HOME; else if (ctrl_pressed) return KEY_CTRL_HOME; else return KEY_HOME; case 0x4f: // End if (shift_pressed && ctrl_pressed) return KEY_SHIFT_CTRL_END; else if (shift_pressed) return KEY_SHIFT_END; else if (ctrl_pressed) return KEY_CTRL_END; else return KEY_END; // 保留原有其他按键的处理 case 0x73: return KEY_CTRL_LEFT; case 0x74: return KEY_CTRL_RIGHT; case 0x8d: return KEY_CTRL_UP; case 0x91: return KEY_CTRL_DOWN; case 0x77: return KEY_CTRL_HOME; case 0x75: return KEY_CTRL_END; case 0x52: return KEY_INS; case 0x53: return KEY_DEL; case 0x49: return KEY_PGUP; case 0x51: return KEY_PGDN; default: return KEY_UNKNOWN; } } // 保留原有0序列的处理逻辑 else if (c1 == 0) { int c2 = getch(); switch(c2) { case 0x9b: return KEY_ALT_LEFT; case 0x9d: return KEY_ALT_RIGHT; case 0x98: return KEY_ALT_UP; case 0xa0: return KEY_ALT_DOWN; case 0x99: return KEY_ALT_PGUP; case 0xa1: return KEY_ALT_PGDN; case 0x97: return KEY_ALT_HOME; case 0x9F: return KEY_ALT_END; case 0x3b: return KEY_F1; case 0x3d: return KEY_F3; case 0x3f: return KEY_F5; default: return KEY_UNKNOWN; } } else { switch(c1) { case 0x08: return KEY_BACKSPACE; case 0x1b: return KEY_ESC; case 0x0d: return KEY_ENTER; case 0x09: return KEY_TAB; default: return c1; } } }
注意事项
- 方法一更适配Windows Terminal,直接复用VT100转义码逻辑,和原编辑器的设计更契合。
- 方法二兼容传统控制台,但
GetKeyState()的状态检测可能存在微小延迟,需确保按键读取与状态检测的时机同步。
内容的提问来源于stack exchange,提问作者Bimo
相关产品推荐
相关产品推荐

