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被声明时,为下面一种。

ReadConsoleInputAReadConsoleInputW声明如下:

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_PRESSED0x0001右Alt键被按下
LEFT_ALT_PRESSED0x0002左Alt键被按下
RIGHT_CTRL_PRESSED0x0004右Ctrl键被按下
LEFT_CTRL_PRESSED0x0008左Ctrl键被按下
SHIFT_PRESSED0x0010Shift键被按下
NUMLOCK_ON0x0020Num Lock状态为开
SCROLLLOCK_ON0x0040Scroll Lock状态为开
CAPSLOCK_ON0x0080Caps Lock状态为开
ENHANCED_KEY0x0100指示该键为拓展键

示例:用户输入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_RECORDEvent.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;
}

效果图如下:
按下Ctrl+C/Break退出循环(好像Exit没用过去式)

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_PRESSED0x0001最左边的按钮被按下
RIGHTMOST_BUTTON_PRESSED0x0002最右边的按钮被按下
FROM_LEFT_2ND_BUTTON_PRESSED0x0004从左往右第2个按钮被按下
FROM_LEFT_3RD_BUTTON_PRESSED0x0008从左往右第3个按钮被按下
FROM_LEFT_4TH_BUTTON_PRESSED0x0010从左往右第4个按钮被按下

注:在控制台快速编辑设置下,MouseEvent的FROM_LEFT_1ST_BUTTON_PRESSED将会受到影响
dwControlKeyState声明见KeyEvent
dwEventFlags有以下可能取值:

标识符说明
0x0000表示鼠标按钮正在按下或释放
MOUSE_MOVED0x0001鼠标位置移动
DOUBLE_CLICK0x0002在一次双击事件中第二次被触发,第一次为普通的单击事件
MOUSE_WHEELED0x0004鼠标的垂直滚轮被移动,如果dwButtonState最高位为0,则滚轮为向前滚,反之为向后滚(相当于用户)
MOUSE_HWHEELED0x0008鼠标的水平滚轮被移动,如果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;

示例如下:
强行锁定控制台大小(效果不好,有闪烁,这里只作示例,如要真正锁定控制台大小,请使用SetWindowLongGetConsoleWindow)

#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显蓝色
示例1
实现代码:

#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的解释到此为止.


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部