一、设置句柄与窗口信息 在Windows操作系统下用C语言编写控制台的窗口界面首先要获取当前标准输入和标准输出设备的句柄。通过调用函数GetStdHandle可以获取当前标准输入以及输出设备的句柄。函数原型为:
1 2 3 4 5 6 7 HANDLE GetStdHandle (DWORD nStdHandle) ;
需要说明的是,句柄 是Windows最常用的一个概念。它通常用来标识 Windows 资源(如菜单、 图标、窗口等)和设备等对象。虽然可以把句柄理解为是一个指针变量类型,但它不是对象所在的地址指针 ,而是作为Windows系统内部表的索引值 来使用的。
调用相关文本界面控制的 API 函数可分为三类。
1.1 示例程序: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <windows.h> #include <conio.h> int main (int argc,char *argv[]) { HANDLE handle_out; CONSOLE_SCREEN_BUFFER_INFO screen_info; COORD pos = {0 , 0 }; handle_out = GetStdHandle (STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo (handle_out, &screen_info); _getch(); FillConsoleOutputCharacter (handle_out, 'A' , screen_info.dwSize.X * screen_info.dwSize.Y, pos, NULL ); CloseHandle (handle_out); return 0 ; }
程序中,COORD 和 CONSOLE_SCREEN_BUFFER_ INFO 是 wincon.h 定义的控制台结构体类型 原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct _COORD { SHORT X; SHORT Y; }COORD; typedef struct _CONSOLE_SCREEN_BUFFER_INFO { COORD dwSize; COORD dwCursorPosition; WORD wAttributes; SMALL_RECT srWindow; COORD dwMaximumWindowSize; }CONSOLE_SCREEN_BUFFER_INFO;
NOTE: 还需要说明的是,虽然在 C++ 中,iostream.h 定义了 cin 和 cout 的标准输入和输出流对象
但它们只能实现基本的输入输出操作,对于控制台窗口界面的控制却无能为力,而且不能与 stdio.h 和 conio.h 友好相处,因为 iostream.h 和它们是 C++ 两套不同的输入 输出操作方式,使用时要特别注意。
二、窗口缓冲区的设置 2.1 控制台窗口操作的API函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 GetConsoleScreenBufferInfo (); GetConsoleTitle (); SetConsoleScreenBufferSize (); SetConsoleTitle (); SetConsoleWindowInfo ();
2.2 示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <conio.h> #define N 255 int main () { HANDLE handle_out; CONSOLE_SCREEN_BUFFER_INFO scbi; COORD size = {80 , 25 }; char strtitle[N]; handle_out = GetStdHandle (STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo (handle_out, &scbi); GetConsoleTitle (strtitle, N); printf ("当前窗口标题为:%s\n" , strtitle); _getch(); SetConsoleTitle ("控制台窗口操作" ); GetConsoleTitle (strtitle, N); printf ("当前窗口标题为:%s\n" , strtitle); _getch(); SetConsoleScreenBufferSize (handle_out, size); _getch(); SMALL_RECT rc = {0 , 0 , 80 -1 , 25 -1 }; SetConsoleWindowInfo (handle_out, 1 , &rc); CloseHandle (handle_out); return 0 ; }
其中,SetConsoleScreenBufferSize 函数指定 新的控制台屏幕缓冲区的大小,以字符 列 和 行 为单位。
指定的宽度和高度不能小于控制台屏幕缓冲区窗口的宽度和高度。指定的大小也不能小于系统允许的最小大小。这个最低取决于控制台当前的字体大小 (由用户选定)。
三、文本属性 设置文本属性使用函数 SetConsoleTextAttribute ,原型如下:
1 2 3 4 BOOL SetConsoleTextAttribute ( HANDLE hConsoleOutput, WORD wAttributes ) ;
3.1 文本属性: 其实就是颜色属性,有 背景色 和 前景色 (就是字符的颜色)两类
每一类只提供三原色 (红,绿,蓝)和 加强色(灰色) ,可与其他颜色搭配使用,使颜色变亮,后面会提到。
最后还有一个反色, 示例 C++ 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <conio.h> const WORD FORE_BLUE = FOREGROUND_BLUE; const WORD FORE_GREEN = FOREGROUND_GREEN; const WORD FORE_RED = FOREGROUND_RED; const WORD FORE_PURPLE = FORE_BLUE | FORE_RED; const WORD FORE_CYAN = FORE_BLUE | FORE_GREEN; const WORD FORE_YELLOW = FORE_RED | FORE_GREEN; const WORD FORE_GRAY = FOREGROUND_INTENSITY; const WORD BACK_BLUE = BACKGROUND_BLUE; const WORD BACK_GREEN = BACKGROUND_GREEN; const WORD BACK_RED = BACKGROUND_RED; const WORD BACK_PURPLE = BACK_BLUE | BACK_RED; const WORD BACK_CYAN = BACK_BLUE | BACK_GREEN; const WORD BACK_YELLOW = BACK_RED | BACK_GREEN; const WORD BACK_GRAY = BACKGROUND_INTENSITY; int main () { HANDLE handle_out = GetStdHandle (STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo (handle_out, &csbi); SetConsoleTextAttribute (handle_out, FORE_BLUE); printf ("蓝色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_RED); printf ("红色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_GREEN); printf ("绿色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_PURPLE); printf ("紫色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_CYAN); printf ("青色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_YELLOW); printf ("黄色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_GRAY); printf ("灰色字符\n" ); SetConsoleTextAttribute (handle_out, FORE_GREEN | FORE_BLUE | FORE_RED); printf ("白色字符\n" ); SetConsoleTextAttribute (handle_out, BACK_BLUE); printf ("蓝色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_RED); printf ("红色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_GREEN); printf ("绿色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_PURPLE); printf ("紫色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_CYAN); printf ("青色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_YELLOW); printf ("黄色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_GRAY); printf ("灰色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_BLUE | BACK_RED | BACK_GREEN); printf ("白色背景\n" ); SetConsoleTextAttribute (handle_out, BACK_GREEN | FORE_RED); printf ("绿色背景与红色字符的混合\n" ); SetConsoleTextAttribute (handle_out, FOREGROUND_INTENSITY | FORE_RED); printf ("亮色的生成,与加强色融合\n" ); return 0 ; }
上述示例程序最好用 C++ 来编译,因为有 C语言 的编译器或者 IDE 不支持上述的定义常量的方式。需要从这个示例中了解的是三原 色的混合是用 C语言 位运算中的按位或 | 运算符,背景颜色与字符颜色的同时定义也是使用这个运算符融合。
另外,将任意颜色与对应的加强色(灰色,有前景和背景两种,需要对应)融合后会成为对应颜色的高亮版,比如红色字符与前景加强色的融合会结合成亮红色。
至于反色,大家可以试试,当我设置了文本属性为反色后,输入字符都不显示了,但是下标还在移动,我估计反色将白色字符变成了黑色字符,与黑色背景一样,所以没有显示出来。至于反色与其他的组合以及其他的颜色组合,还需要大家一起探索、、、
四、文本属性 4.1 常用的文本输出函数 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BOOL FillConsoleOutputAttribute ( HANDLE hConsoleOutput, WORD wAttribute, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten ) ; BOOL FillConsoleOutputCharacter ( HANDLE hConsoleOutput, TCHAR cCharacter, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten ) ; BOOL WriteConsoleOutputCharacter ( HANDLE hConsoleOutput, LPCTSTR lpCharacter, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten ) ;
另外再介绍一个表示区域的结构体,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef struct _SMALL_RECT { SHORT Left; SHORT Top; SHORT Right; SHORT Bottom; } SMALL_RECT;
4.2 示例程序 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <conio.h> int main () { char *str = "Hello World!" ; int len = strlen (str), i; WORD shadow = BACKGROUND_INTENSITY; WORD text = BACKGROUND_GREEN | BACKGROUND_INTENSITY; HANDLE handle_out = GetStdHandle (STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo (handle_out, &csbi); SMALL_RECT rc; COORD posText; COORD posShadow; rc.Top = 8 ; rc.Bottom = rc.Top + 4 ; rc.Left = (csbi.dwSize.X - len) / 2 - 2 ; rc.Right = rc.Left + len + 4 ; posText.X = rc.Left; posText.Y = rc.Top; posShadow.X = posText.X + 1 ; posShadow.Y = posText.Y + 1 ; for (i=0 ; i<5 ; ++i) { FillConsoleOutputAttribute (handle_out, shadow, len + 4 , posShadow, NULL ); posShadow.Y++; } for (i=0 ; i<5 ; ++i) { FillConsoleOutputAttribute (handle_out, text, len + 4 , posText, NULL ); posText.Y++; } posText.X = rc.Left + 2 ; posText.Y = rc.Top + 2 ; WriteConsoleOutputCharacter (handle_out, str, len, posText, NULL ); SetConsoleTextAttribute (handle_out, csbi.wAttributes); CloseHandle (handle_out); return 0 ; }
以上样例在Code::Blocks 13.12中编译通过。
五、文本移动 5.1文本移动的函数 如下:
1 2 3 4 5 6 7 8 BOOL ScrollConsoleScreenBuffer ( HANDLE hConsoleOutput, const SMALL_RECT *lpScrollRectangle, const SMALL_RECT *lpClipRectangle, COORD dwDestinationOrigin, const CHAR_INFO *lpFill ) ;
5.2 文本移动样例: 下面来看一个移动文本的样例程序,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <conio.h> #include <Windows.h> #include <stdlib.h> int main () { HANDLE handle_out = GetStdHandle (STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; SMALL_RECT scroll; COORD pos = {0 , 5 }; CHAR_INFO chFill; GetConsoleScreenBufferInfo (handle_out, &csbi); chFill.Char.AsciiChar = ' ' ; chFill.Attributes = csbi.wAttributes; printf ("00000000000000000000000000000\n" ); printf ("11111111111111111111111111111\n" ); printf ("22222222222222222222222222222\n" ); printf ("33333333333333333333333333333\n" ); scroll.Left = 1 ; scroll.Top = 1 ; scroll.Right = 10 ; scroll.Bottom = 2 ; ScrollConsoleScreenBuffer (handle_out, &scroll, NULL , pos, &chFill); return 0 ; }
在上面的样例程序中,裁剪区域是整个控制台窗口的屏幕缓冲区
现在如果我们把裁剪区域设定为与移动区域一样,也就是说 ScrollConsoleScreenBuffer 函数的第三个参数也改成 &scroll ,那么结果会怎么样呢?
为什么会发生这种现象呢?
很明显示因为裁剪区域的设定问题,现在我们把裁剪区域依旧设定成移动区域,但是我们只把移动区域下移一行而不是移动在别的位置,看看会有什么现象发生?
现在我们应该可以猜想出结论了,别急,再做一个实验,现在我们将裁减区域又重新改为整个屏幕缓冲区,看看会有什么样的现象发生?
再来最后一个实验,我们将裁减区域减小为移动区域的上半部分,继续执行下移一行的操作,看看最终结果会怎么样?
5.3 结论 现在,我们可以得出几个结论:
总的归纳来说也就是原来是什么样子,文本移动后还是什么样子,不会改变。
总的归纳来说也就是完全受文本移动的影响,移动过来就被覆盖,被移走就由设定的字符来填充
六、键盘事件 输入事件中的键盘事件通常有字符事件和按键事件,这些事件的附带信息构成了键盘输入的信息
而想要读取这些信息,是要通过 API 函数 ReadConsoleInput 来获取的,函数原型如下:
1 2 3 4 5 6 7 BOOL ReadConsoleInput ( HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead ) ;
下面介绍几个和读取键盘输入事件有关的结构体,各结构体原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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; typedef struct _KEY_EVENT_RECORD { BOOL bKeyDown; WORD wRepeatCount; WORD wVirtualKeyCode; WORD wVirtualScanCode; union { WCHAR UnicodeChar; CHAR AsciiChar; } uChar; DWORD dwControlKeyState; } KEY_EVENT_RECORD;
当输入事件为键盘事件时,事件类型就为键盘事件,为其他事件时,事件类型就为对应的事件。
另外,键盘上每个有意义的键都对应着一个唯一的扫描码,虽然扫描码可以作为键的标识,但是它是依赖于具体的设备 的。
因此,在应用程序中,使用的往往是与具体设备无关的虚拟键代码 。这种虚拟键代码是一种与具体设备无关的键盘编码。而控制键状态比如大写锁定开启状态,Ctrl键按下状态等
6.1 部分常用虚拟键代码表 下面是部分常用虚拟键代码表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
以上是部分常用的微软虚拟键盘码表,想要知道更详细的,请参见MSDN。其中各个虚拟键的具体使用情况根据各人编译器或IDE等的不同而有所差异。
6.2 样例程序: 实现按 Esc 键就输出 Esc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <conio.h> #define true 1 #define false 0 int main () { HANDLE handle_in = GetStdHandle (STD_INPUT_HANDLE); INPUT_RECORD keyrec; DWORD res; for (;;) { ReadConsoleInput (handle_in, &keyrec, 1 , &res); if (keyrec.EventType == KEY_EVENT) { if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) { printf ("Esc " ); } } } return 0 ; }
在上面的样例程序中,当你按下Esc键后又马上释放,程序会输出两次Esc,因为有两次事件的虚拟键代码都是Esc键的代码,一次是按下,一次是释放。
如果要实现按下键后出现反应,释放不出现反应,需要监听事件类型,将条件改为:
1 2 3 if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE && keyrec.Event.KeyEvent.bKeyDown == true )
即可
根据控制键的状态我们可以实现不同的状态输出不同的值以及组合键的实现,下面的样例程序在大写锁定打开时输入 A 键则输出大写字母 A ,否则输出小写字母 a 。而在 Shift 键被按下的状态是则输出 Shift + A 以及 Shift + a 。样例程序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <conio.h> #define true 1 #define false 0 int main () { HANDLE handle_in = GetStdHandle (STD_INPUT_HANDLE); INPUT_RECORD keyrec; DWORD res; for (;;) { ReadConsoleInput (handle_in, &keyrec, 1 , &res); if (keyrec.EventType == KEY_EVENT) { if (keyrec.Event.KeyEvent.wVirtualKeyCode == 'A' && keyrec.Event.KeyEvent.bKeyDown == true ) { if (keyrec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED) { printf ("Shift+" ); } if (keyrec.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON) { printf ("A " ); } else { printf ("a " ); } } } } return 0 ; }
由上例需要了解到的是,各个控制键状态的的确定并不是使用等于符号==而是按位与&运算符,因为在同一时刻可能有多种控制键状态值,比如各种锁定都被打开且各种控制键也被同时按下。使用&运算符则显得尤其高明,方便查询各个控制键的状态而不使之出现冲突。呵呵,不服不行啊,感慨一下,还是要多学习一下别人高明的地方,比如灵活运用位运算符实现各种功能等等······
七、鼠标事件 上次讲的是键盘事件,这次我们介绍鼠标事件。下面先介绍下鼠标事件的结构体以及相关信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 typedef struct _MOUSE_EVENT_RECORD { COORD dwMousePosition; DWORD dwButtonState; DWORD dwControlKeyState; DWORD dwEventFlags; } MOUSE_EVENT_RECORD;
7.1 样例程序 实现在控制台窗口缓冲区的最下面一行显示当前鼠标在缓冲区的坐标,单击左键在当前鼠标位置输出字母 A ,单击右键则输出字母 B ,双击任何鼠标键退出的功能。程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <stdio.h> #include <windows.h> #include <conio.h> HANDLE handle_in; HANDLE handle_out; CONSOLE_SCREEN_BUFFER_INFO csbi; void DisplayMousePosition (COORD pos) ; void gotoxy (int x, int y) ; int main () { handle_in = GetStdHandle (STD_INPUT_HANDLE); handle_out = GetStdHandle (STD_OUTPUT_HANDLE); INPUT_RECORD mouserec; DWORD res; COORD pos; COORD size = {80 , 25 }; GetConsoleScreenBufferInfo (handle_out, &csbi); SetConsoleScreenBufferSize (handle_out, size); for (;;) { ReadConsoleInput (handle_in, &mouserec, 1 , &res); pos = mouserec.Event.MouseEvent.dwMousePosition; gotoxy (0 , 24 ); DisplayMousePosition (pos); if (mouserec.EventType == MOUSE_EVENT) { gotoxy (pos.X, pos.Y); if (mouserec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED) { putchar ('A' ); } if (mouserec.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED) { putchar ('B' ); } if (mouserec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK) { break ; } } } CloseHandle (handle_out); CloseHandle (handle_in); return 0 ; } void DisplayMousePosition (COORD pos) { COORD dis = {0 , 24 }; WORD att = FOREGROUND_GREEN | FOREGROUND_INTENSITY; GetConsoleScreenBufferInfo (handle_out, &csbi); printf ("X = %3d, Y = %3d" , (int )pos.X, (int )pos.Y); FillConsoleOutputAttribute (handle_out, att, 16 , dis, NULL ); return ; } void gotoxy (int x, int y) { COORD pos = {x, y}; SetConsoleCursorPosition (handle_out, pos); }
附上用本程序写的Hello world!的图:
注意:当使用system函数后鼠标事件无法正常发生。
———————————————— 版权声明:本文为CSDN博主「KKK_Kiral」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liluo_2951121599/article/details/66474233
Comments