开发简要
控制台开发首先是获得控制台的句柄,它有两个常用句柄,一个输入,一个输出,通过GetStdHandle函数获得。
STD_INPUT_HANDLE | 标准输入设备。 输入缓冲区 |
STD_OUTPUT_HANDLE | 标准输出设备。 控制台屏幕缓冲区 |
STD_ERROR_HANDLE | 标准错误设备。 控制台屏幕缓冲区 |
通过以下代码可以获取句柄
HANDLE g_istd = GetStdHandle(STD_INPUT_HANDLE);// 控制台输入句柄
HANDLE g_ostd = GetStdHandle(STD_OUTPUT_HANDLE); // 控制台输出句柄
输入设备
一般来说通过输入缓冲区可以用来获取 键盘输入,鼠标输入之类信息。使用函数ReadConsoleInput 从输入缓冲区中读取输入事件,一般提前使用GetNumberOfConsoleInputEvents 函数,来确定输入缓冲区中未读输入事件的数目。
ReadConsoleInput
参数:
hConsoleInput [in]
控制台输入缓冲区的句柄。
lpBuffer [out]
用来接收输入信息的结构体数组。类型为INPUT_RECORD。
nLength [in]
lpBuffer 数组的大小。
lpNumberOfEventsRead [out]
返回读取的输入事件数。
通过该函数,可以获取到键盘的方向按键信息
DWORD nread;
INPUT_RECORD* ibuf = NULL;
// 判读是否有输入事件
if (!GetNumberOfConsoleInputEvents(g_istd, &nread))
{
printf("GetNumberOfConsoleInputEvents error\n");
return;
}
if (!nread)
{
return;
}
ibuf = new INPUT_RECORD[nread];
// 读取输入
if (!ReadConsoleInput(
g_istd, // input buffer handle
ibuf, // buffer to read into
nread, // size of read buffer
&nread)) // number of records read
{
printf("ReadConsoleInput error\n");
return;
}
// 判断事件类型
for (int i = 0; i < nread; i++)
{
switch (ibuf[i].EventType)
{
case KEY_EVENT: // keyboard input
if (ibuf[i].Event.KeyEvent.bKeyDown)
{
switch (ibuf[i].Event.KeyEvent.wVirtualKeyCode)
{
case VK_LEFT:
case VK_RIGHT:
case VK_UP:
case VK_DOWN:
// 执行操作
break;
default:
break; // 直接退出。
}
}
break;
default:
break;
}
}
delete[] ibuf;
输出设备
一般来说可认为就是我们看到的控制台窗口本尊。通过ReadConsoleOutput 可读取屏幕缓冲区每格单元格的字符和属性,这些决定了控制台显示的内容、颜色等
在对控制台的窗口进行操作的时候,可以把窗口看成是一格一格的类似excel的单元格的存在,只是看不到线。通过GetConsoleScreenBufferInfo等函数 可以获取屏幕缓冲区的信息。
通过WriteConsoleOutput等函数,可以在屏幕上输出内容、颜色等
单元格属性
Attribute | 含义 |
---|---|
FOREGROUND_BLUE | 文本颜色包含蓝色。 |
FOREGROUND_GREEN | 文本颜色包含绿色。 |
FOREGROUND_RED | 文本颜色包含红色。 |
FOREGROUND_INTENSITY | 文本颜色增强。 |
BACKGROUND_BLUE | 背景色包含蓝色。 |
BACKGROUND_GREEN | 背景色包含绿色。 |
BACKGROUND_RED | 背景色包含红色。 |
BACKGROUND_INTENSITY | 背景色增强。 |
COMMON_LVB_LEADING_BYTE | 前导字节。 |
COMMON_LVB_TRAILING_BYTE | 尾随字节。 |
COMMON_LVB_GRID_HORIZONTAL | 顶部水平。 |
COMMON_LVB_GRID_LVERTICAL | 左垂直。 |
COMMON_LVB_GRID_RVERTICAL | 右垂直。 |
COMMON_LVB_REVERSE_VIDEO | 反转前景和背景属性。 |
COMMON_LVB_UNDERSCORE | 下划线。 |
单元格属性可以组合使用,比如FOREGROUND_BLUE | FOREGROUND_GREEN |FOREGROUND_RED 组合表示,输出文本颜色为白色,未指定背景颜色,则默认背景是黑色。
GetConsoleScreenBufferInfo
hConsoleOutput [in]
控制台屏幕缓冲区的句柄
lpConsoleScreenBufferInfo [out]
输出获取到的屏幕缓冲区信息。该结构体成员如下:
- dwSize 控制台屏幕缓冲区的大小,可以理解成屏幕由x列y行的单元格矩阵组成。
- dwCursorPosition 当前控制台光标所在的行列坐标。
- wAttributes 当前屏幕输出文本时所使用的属性
- srWindow 包含显示窗口左上角和右下角的控制台屏幕缓冲区坐标。
- dwMaximumWindowSize 控制台窗口的最大大小
WriteConsoleOutput
hConsoleOutput [in]
控制台屏幕缓冲区的句柄。
lpBuffer [in]
要写入到控制台屏幕缓冲区的数据数组,该数组每项对应屏幕缓冲区一个“单元格”,用来设置该单元格的文本和颜色等。
lpBuffer可以想象成是屏幕缓冲区的一块画布。通过dwBufferSize,dwBufferCoord来指定画布与缓冲区的位置关系。最好lpBuffer是与屏幕缓冲区大小一致(GetConsoleScreenBufferInfo 获得的dwSize),方便定位某个“单元格”。
dwBufferSize [in]
lpBuffer 数组的大小
dwBufferCoord [in]
这个参数很变扭,感觉这个点表示,屏幕缓冲区左上角位于画布上的位置。一般设成{0, 0}
lpWriteRegion [in, out]
指定绘制到某个区域。
可以理解成把上面的画布绘制到该区域
通过该函数,可以绘制屏幕
CONSOLE_SCREEN_BUFFER_INFO csbi;
// 获取屏幕缓冲区信息
GetConsoleScreenBufferInfo(g_ostd, &csbi);
int len = 5;
COORD* pt = new COORD[5];
// 设定要修改的单元格坐标
for (int i = 0; i < len; i++)
{
pt[i].X = i + 1;
pt[i].Y = 1;
}
// 设定蓝色背景
WORD attr = BACKGROUND_BLUE ;
COORD size = csbi.dwSize;
CHAR_INFO* buf = NULL; // 要写入缓冲区的数据数组
COORD bufsize = { size.X, size.Y }; // 缓冲区大小
COORD coord = { 0, 0 }; // 无偏移
SMALL_RECT rect = { 0, 0, size.X - 1, size.Y - 1 }; // 绘制区域,控制绘制位置
buf = new CHAR_INFO[size.X * size.Y];
// 获取当前缓冲区的数据
ReadConsoleOutput(g_ostd, buf, bufsize, coord, &rect);
// 修改pt所指向的单元格的属性
for (int i = 0; i < len; i++)
{
int bufi = size.X * pt[i].Y + pt[i].X;
if (bufi >= 0 && bufi < size.X * size.Y
&& pt[i].X >= 0 && pt[i].X < size.X)
{
buf[bufi].Char.UnicodeChar = TEXT(' '); // 设置缓冲区单元格文本,这里是空格
buf[bufi].Attributes = attr; // 设置缓冲区单元格属性
}
}
// 输出到屏幕
WriteConsoleOutput(g_ostd, buf, bufsize, coord, &rect);
delete[] buf;
WriteConsole
用于输出一段文字到屏幕
一般先使用SetConsoleCursorPosition 函数设定光标的位置,即设定WriteConsole输出起始位置
此外,使用FillConsoleOutputAttribute 函数可以设定输出文本的颜色。
WORD attr;
COORD coord;
DWORD written;
DWORD length;
// 设定文本输出属性,蓝底黄字
attr = FOREGROUND_GREEN | FOREGROUND_RED | BACKGROUND_BLUE;
// 设定输出位置
coord = { SHORT(g_size.X / 2) , SHORT(g_size.Y / 2) };
TCHAR str[32]=TEXT("Hello World");
length = lstrlen(str);
// 设定光标位置
SetConsoleCursorPosition(g_ostd, coord);
// 输出文本
if (!WriteConsole(g_ostd, str, length, &written, NULL))
return;
// 设定输出颜色
if (!FillConsoleOutputAttribute(g_ostd, attr, length, coord, &written))
return;
示例 显示一个进度条
/// <summary>
/// 在控制台上显示进度条
/// </summary>
/// <param name="hConsole">控制台</param>
/// <param name="percent">需要显示的进度条百分比</param>
/// <param name="back">光标是否回到起始位置,方便下一次刷新进度条,或者指向下一行</param>
/// <param name="show">是否显示光标</param>
void PrintProgress(HANDLE ostd, int percent, BOOL back = TRUE, BOOL show = FALSE)
{
COORD cur_coord = { 0, 0 }; // 鼠标坐标
DWORD written;
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD number;
if (ostd == NULL)
{
ostd = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (percent > 100)
percent = 100;
else if (percent < 0)
percent = 0;
if (!GetConsoleScreenBufferInfo(ostd, &csbi))
{
return;
}
WORD attr;
// 输出属性
attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE;
number = csbi.dwSize.X * percent / 100; // 获得百分比对应的输出cells数量
cur_coord = csbi.dwCursorPosition; // 进度条起始位置
TCHAR str[10];
swprintf_s(str, 10, TEXT("%d%%"), percent);
// 输出进度条
if (!FillConsoleOutputCharacter(ostd,(TCHAR)' ', number,cur_coord, &written))
{
return;
}
// 输出百分比
WriteConsole(ostd, str, lstrlen(str), &written, NULL);
// 给进度条上色
if (!FillConsoleOutputAttribute(ostd, attr, number, cur_coord, &written))
{
return;
}
if (back) // 回到进度条开头
{
SetConsoleCursorPosition(ostd, cur_coord);
}
else // 指向下一行
{
cur_coord.X += 0;
cur_coord.Y += 1;
SetConsoleCursorPosition(ostd, cur_coord);
}
// 隐藏或显示光标
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(ostd, &cursorInfo);
cursorInfo.bVisible = show;
SetConsoleCursorInfo(ostd, &cursorInfo);
}
效果测试
int i=0;
while (1) {
PrintProgress(NULL, i++);
Sleep(100);
}