Windows控制台输入
在Windows控制台中,我们通常要处理一些用户输入,例如按键,鼠标(不常用,但是有),缓冲区更改等。
Windows系统为了减轻程序员的负担,提供了ReadConsoleInput函数
在docs.microsoft.com中这样描述ReadConsoleInput:
Reads data from a console input buffer and removes it from the buffer.
从一个控制台缓冲区读取数据并将其从缓冲区移除。
在头文件中,ReadConsoleInput有两种可能的声明:
#define ReadConsoleInput ReadConsoleInputA
#define ReadConsoleInput ReadConsoleInputW
当Unicode被声明时,为下面一种。
ReadConsoleInputA或ReadConsoleInputW声明如下:
BOOL WINAPI ReadConsoleInput(_In_ HANDLE hConsoleInput, //控制台输入句柄,由GetStdHandle获得_Out_ PINPUT_RECORD lpBuffer, //指向INPUT_RECORD(可以为数组)的指针_In_ DWORD nLength, //接收消息的个数_Out_ LPDWORD lpNumberOfEventsRead //用于存储成功获取消息个数的指针
);
函数成功,则返回非零值,反之为零。
其中,结构体INPUT_RECORD声明如下:
typedef struct _INPUT_RECORD {WORD EventType; //事件类型union {KEY_EVENT_RECORD KeyEvent; //存储键盘事件的结构体MOUSE_EVENT_RECORD MouseEvent; //鼠标事件WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;//缓冲区大小事件MENU_EVENT_RECORD MenuEvent; //菜单事件FOCUS_EVENT_RECORD FocusEvent; //焦点事件} Event;
} INPUT_RECORD;
其中,EventType取值如下:
#define KEY_EVENT 0x0001
#define MOUSE_EVENT 0x0002
#define WINDOW_BUFFER_SIZE_EVENT 0x0004
#define MENU_EVENT 0x0008
#define FOCUS_EVENT 0x0010
1.KeyEvent
这是最常用的输入事件
KEY_EVENT_RECORD结构体声明如下:
typedef struct _KEY_EVENT_RECORD {BOOL bKeyDown; //指示按键是否按下,对于Ctrl+C/Break无效WORD wRepeatCount; //按键重复次数,不累加WORD wVirtualKeyCode; //虚拟键码WORD wVirtualScanCode; //虚拟扫描码union {WCHAR UnicodeChar; //Unicode码,ReadConsoleInputW使用CHAR AsciiChar; //ASCII码,ReadConsoleInputA使用} uChar;DWORD dwControlKeyState;//控制键状态
} KEY_EVENT_RECORD, *PKEY_EVENT_RECORD;
控制键状态dwControlKeyState可由以下值合并:
| 标识符 | 值 | 说明 |
|---|---|---|
| RIGHT_ALT_PRESSED | 0x0001 | 右Alt键被按下 |
| LEFT_ALT_PRESSED | 0x0002 | 左Alt键被按下 |
| RIGHT_CTRL_PRESSED | 0x0004 | 右Ctrl键被按下 |
| LEFT_CTRL_PRESSED | 0x0008 | 左Ctrl键被按下 |
| SHIFT_PRESSED | 0x0010 | Shift键被按下 |
| NUMLOCK_ON | 0x0020 | Num Lock状态为开 |
| SCROLLLOCK_ON | 0x0040 | Scroll Lock状态为开 |
| CAPSLOCK_ON | 0x0080 | Caps Lock状态为开 |
| ENHANCED_KEY | 0x0100 | 指示该键为拓展键 |
示例:用户输入q键时退出程序
#include
int main(){INPUT_RECORD sinfo;DWORD recnum;HANDLE hIn=GetStdHandle(STD_OUTPUT_HANDLE);while (GetConsoleInput(hIn, &sinfo, 1, &recnum)){if (sinfo.EventType != KEY_EVENT) continue;//无视其他类型输入//判断按键状态(以防按键被触发两次)//if (!sinfo.Event.KeyEvent.bKeyDown) continue;if (sinfo.Event.KeyEvent.uChar.UnicodeChar == 'q') break;}
}
在获取键盘事件时,有一点比较特殊:
用户输入Ctrl+C/Break时,ReadConsoleInput会抛出C++错误,C并不接收C++错误(VS会接收),但此时ReadConsoleInput返回的INPUT_RECORD中Event.KeyEvent.bKeyDown为0,即使Ctrl+C在按下状态
虚拟键码低8位为3(A1->B2->C3,以此推例,Break视作C),第10(RIGHT_CTRL_PRESSED)或第11(LEFT_CTRL_PRESSED)位为1
0000xx00 00000011
所以,接收Ctrl+C/Break退出循环的方法如下
#include
#include
BOOL ReceiveHandler(DWORD dwCtrlType);
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);//由于Ctrl+C/Break会发送事件信号,为防止默认处理程序关闭,设置处理函数SetConsoleCtrlHandler((PHANDLER_ROUTINE)&ReceiveHandler, TRUE);while (1) {DWORD num; int isexit = 0;GetNumberOfConsoleInputEvents(hIn, &num);//获取用户输入个数while (num--) {//如果有,一个一个处理INPUT_RECORD sinfo;DWORD recnum;ReadConsoleInput(hIn, &sinfo, 1, &recnum);//if (!recnum) { num++; continue ;} //如果你担心的话加上这一句if (sinfo.EventType != KEY_EVENT) continue;//在这里只处理键盘事件//无视bKeyDownif (sinfo.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {if (sinfo.Event.KeyEvent.uChar.UnicodeChar == 3) {//你可以添加Ctrl+C/Break处理事件isexit = 1;break;}//由于有LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED//按下并松开Ctrl时这里只触发一次事件}}if (isexit) break;//退出两层循环//这里添加要循环处理的事件,这里为SleepSleep(10);}puts("You Pressed The Ctrl+C/Break And Exit The Loop");return 0;
}
BOOL ReceiveHandler(DWORD dwCtrlType) {switch (dwCtrlType) {case CTRL_BREAK_EVENT:case CTRL_C_EVENT:return TRUE;//防止默认关闭}return FALSE;
}
效果图如下:

2.MouseEvent
Mouse Event只有在Mouse Mode模式才会被触发(ENABLE_MOUSE_INPUT)
该值可由GetConsoleMode获取,可由SetConsoleMode设置
MOUSE_EVENT_RECORD定义如下:
typedef struct _MOUSE_EVENT_RECORD {COORD dwMousePosition; //鼠标位置(相对于控制台且只能获取所在屏幕缓冲区坐标而非像素坐标)DWORD dwButtonState; //鼠标按钮状态DWORD dwControlKeyState;//控制键状态DWORD dwEventFlags; //事件标识符
} MOUSE_EVENT_RECORD, *PMOUSE_EVENT_RECORD;
其中dwButtonState用法如下:
| 标识符 | 值 | 说明 |
|---|---|---|
| FROM_LEFT_1ST_BUTTON_PRESSED | 0x0001 | 最左边的按钮被按下 |
| RIGHTMOST_BUTTON_PRESSED | 0x0002 | 最右边的按钮被按下 |
| FROM_LEFT_2ND_BUTTON_PRESSED | 0x0004 | 从左往右第2个按钮被按下 |
| FROM_LEFT_3RD_BUTTON_PRESSED | 0x0008 | 从左往右第3个按钮被按下 |
| FROM_LEFT_4TH_BUTTON_PRESSED | 0x0010 | 从左往右第4个按钮被按下 |
注:在控制台快速编辑设置下,MouseEvent的FROM_LEFT_1ST_BUTTON_PRESSED将会受到影响
dwControlKeyState声明见KeyEvent
dwEventFlags有以下可能取值:
| 标识符 | 值 | 说明 |
|---|---|---|
| 无 | 0x0000 | 表示鼠标按钮正在按下或释放 |
| MOUSE_MOVED | 0x0001 | 鼠标位置移动 |
| DOUBLE_CLICK | 0x0002 | 在一次双击事件中第二次被触发,第一次为普通的单击事件 |
| MOUSE_WHEELED | 0x0004 | 鼠标的垂直滚轮被移动,如果dwButtonState最高位为0,则滚轮为向前滚,反之为向后滚(相当于用户) |
| MOUSE_HWHEELED | 0x0008 | 鼠标的水平滚轮被移动,如果dwButtonState最高位为0,则滚轮为向右滚,反之为向左滚(相对于用户) |
示例如下:
控制台画图:用0代替,左键按下时画,双击退出
在此之前,请先将快速编辑模式关掉

以下代码在调试时未成功,但在直接运行或命令提示符打开时成功
#include
#include
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//设置鼠标模式DWORD dwMode;GetConsoleMode(hOut, &dwMode);SetConsoleMode(hOut, ENABLE_MOUSE_INPUT | dwMode);INPUT_RECORD inp; DWORD recnum;while (ReadConsoleInput(hIn, &inp, 1, &recnum)) {if (inp.EventType != MOUSE_EVENT) continue;//这里只接受鼠标事件if (inp.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) {//当用户左键被按下SetConsoleCursorPosition(hOut, inp.Event.MouseEvent.dwMousePosition);putchar('0');}if (inp.Event.MouseEvent.dwEventFlags & DOUBLE_CLICK) {SetConsoleCursorPosition(hOut, (COORD) { 0, 0 });//设置光标为{0,0}puts("You Exited The Painting");getchar();return 0;}}
}
效果图:

3.WindowBufferSizeEvent
缓冲区大小更改时会触发该事件
在程序进入控制台时不会引发事件
WINDOW_BUFFER_SIZE_EVENT声明如下:
typedef struct _WINDOW_BUFFER_SIZE_RECORD {COORD dwSize;//缓冲区更改大小
} WINDOW_BUFFER_SIZE_RECORD, *PWINDOW_BUFFER_SIZE_RECORD;
示例如下:
强行锁定控制台大小(效果不好,有闪烁,这里只作示例,如要真正锁定控制台大小,请使用SetWindowLong与GetConsoleWindow)
#include
#include
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);INPUT_RECORD inp; DWORD recnum;//在程序启动时调整控制台大小SetConsoleScreenBufferSize(hOut, (COORD) { 50, 25 });//设置缓冲区大小SMALL_RECT rect = { 0,0,49,24 };SetConsoleWindowInfo(hOut, TRUE, &rect);//设置显示缓冲区while (ReadConsoleInput(hIn, &inp, 1, &recnum)) {if (inp.EventType != WINDOW_BUFFER_SIZE_EVENT) continue;//这里只处理缓冲区事件if (inp.Event.WindowBufferSizeEvent.dwSize.X != 50 || inp.Event.WindowBufferSizeEvent.dwSize.Y != 25) {//不符合大小时引发SetConsoleScreenBufferSize(hOut, (COORD) { 50, 25 });SMALL_RECT rect = { 0,0,49,24 };SetConsoleWindowInfo(hOut, TRUE, &rect);}}return 0;
}
效果图:

4.MenuEvent
当用户打开或关闭时引发
MENU_EVENT_RECORD声明如下:
typedef struct _MENU_EVENT_RECORD {UINT dwCommandId;//命令ID,保留,但有赋值
} MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
示例如下:
#include
#include
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);INPUT_RECORD inp; DWORD recnum;while (ReadConsoleInput(hIn, &inp, 1, &recnum)) {if (inp.EventType != MENU_EVENT) continue;switch (inp.Event.MenuEvent.dwCommandId) {case 278:puts("You Open The Menu");break;case 287:puts("You Close The Menu");break;}}return 0;
}
效果如下:

5.FocusEvent
焦点事件,当控制台获取或失去焦点时触发
FOCUS_EVENT_RECORD声明如下:
typedef struct _FOCUS_EVENT_RECORD {BOOL bSetFocus;//指示是否获得焦点
} FOCUS_EVENT_RECORD, *PFOCUS_EVENT_RECORD;
示例如下:
#include
#include
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);INPUT_RECORD inp; DWORD recnum;while (ReadConsoleInput(hIn, &inp, 1, &recnum)) {if (inp.EventType != FOCUS_EVENT) continue;if (inp.Event.FocusEvent.bSetFocus) {puts("Got The Focus");}else puts("Lost The Focus");}return 0;
}
效果图如下:

到此,有关ReadConsoleInput的教程就结束了。
如有兴趣,可尝试完成以下任务.
实现不同颜色画图,Ctrl+C退出
其中按A显红色,B显绿色,C显蓝色

实现代码:
#include
#include
int main() {HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);INPUT_RECORD inp; DWORD recnum; int isexit = 0;DWORD mode;GetConsoleMode(hOut, &mode);SetConsoleMode(hOut, mode | ENABLE_MOUSE_INPUT);while (ReadConsoleInput(hIn, &inp, 1, &recnum)) {switch (inp.EventType) {case KEY_EVENT:if (inp.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {if (inp.Event.KeyEvent.uChar.UnicodeChar == 3) {isexit = 1;}break;;}if (inp.Event.KeyEvent.bKeyDown) {switch (inp.Event.KeyEvent.uChar.UnicodeChar) {case 'A':case 'a'://redSetConsoleTextAttribute(hOut, BACKGROUND_RED);break;case 'B':case 'b':SetConsoleTextAttribute(hOut, BACKGROUND_GREEN);break;case 'C':case 'c':SetConsoleTextAttribute(hOut, BACKGROUND_BLUE);break;}}break;case MOUSE_EVENT:if (inp.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) {SetConsoleCursorPosition(hOut, inp.Event.MouseEvent.dwMousePosition);putchar(' ');}break;}if (isexit) break;}return 0;
}
ReadConsoleInput的解释到此为止.
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
