如何完美控制控制台输出

1 前言

正常情况下,我们在控制台程序中只关注程序的输入和输出,而不在意输出的格式、光标位置等。
因此,当我们想要完美控制程序的输入输出时,就必须要使用系统提供的操作接口,来实现我们的目标。
由于linux平台的控制台显示完全由ANSI 转义序列控制,在这里仅仅讨论windows平台的实现。

2 接口

window系统提供了大量由于操作控制台的函数,以下列举了一些常用函数。

功能 说明
CreateConsoleScreenBuffer 创建控制台屏幕缓冲区。
FillConsoleOutputCharacter 将字符写入控制台屏幕缓冲区指定的次数。
FlushConsoleInputBuffer 刷新控制台输入缓冲区。
GetConsoleCursorInfo 检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
GetConsoleScreenBufferInfo 检索有关指定控制台屏幕缓冲区的信息。
GetConsoleWindow 检索与调用进程关联的控制台使用的窗口句柄。
GetNumberOfConsoleInputEvents 检索控制台的输入缓冲区中未读输入记录的数目。
GetStdHandle 检索标准输入、标准输出或标准错误设备的句柄。
PeekConsoleInput 从指定的控制台输入缓冲区读取数据,而不将其从缓冲区中删除。
ReadConsole 从控制台输入缓冲区读取字符输入,并将其从缓冲区中删除。
ReadConsoleInput 从控制台输入缓冲区读取数据并将其从缓冲区中删除。
ReadConsoleOutput 从控制台屏幕缓冲区中的字符单元格矩形块读取字符和颜色属性数据。
ReadConsoleOutputCharacter 从控制台屏幕缓冲区的连续单元格复制多个字符。
ScrollConsoleScreenBuffer 移动屏幕缓冲区中的数据块。
SetConsoleActiveScreenBuffer 将指定的屏幕缓冲区设置为当前显示的主机屏幕缓冲区。
SetConsoleCursorInfo 为指定的控制台屏幕缓冲区设置光标的大小和可见性。
SetConsoleCursorPosition 设置指定控制台屏幕缓冲区中的光标位置。
SetConsoleMode 设置控制台输入缓冲区的输入模式或控制台屏幕缓冲区的输出模式。
SetConsoleOutputCP 设置与调用进程关联的控制台使用的输出代码页。
SetConsoleScreenBufferSize 更改指定控制台屏幕缓冲区的大小。
SetConsoleTitle 设置当前控制台窗口的标题。
SetConsoleWindowInfo 设置控制台屏幕缓冲区窗口的当前大小和位置。
SetStdHandle 设置标准输入、标准输出或标准错误设备的句柄。
WriteConsole 从当前光标位置开始,将字符串写入控制台屏幕缓冲区。
WriteConsoleInput 将数据直接写入控制台输入缓冲区。
WriteConsoleOutput 将字符和颜色属性数据写入控制台屏幕缓冲区中的指定矩形字符单元格块。
WriteConsoleOutputCharacter 将多个字符复制到控制台屏幕缓冲区的连续单元格。

3 示例

3.1 创建新的控制台

使用读写权限GENERIC_READ | GENERIC_WRITE创建控制台缓冲区,同时设置读共享FILE_SHARE_READ。(此处必须设置读共享,否终在替换缓冲区后无法从标准输入获取输入信息。)
创建句柄后,将创建的句柄设置为当前活动缓冲区,在不影响旧的缓冲区前提下,用新的屏幕替代。

  HANDLE hOutput = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
                                      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                                      CONSOLE_TEXTMODE_BUFFER, NULL);
 
  // replace STD_OUTPUT_HANDLE with hOutput
  SetConsoleActiveScreenBuffer(hOutput);
3.2 设置无滚动条

Windows并未提供滚动条设置的相关接口,但是分析可以发现,之所以出现滚动条,是因为屏幕缓冲区的大小比实际窗口的大,因此才会出现纵向滚动条。
在此前提下,我们可以通过设置屏幕缓冲区的大小和窗口大小保持一致来隐藏滚动条。

  CONSOLE_SCREEN_BUFFER_INFO screen_info;
  // 获取当前缓冲区信息
  GetConsoleScreenBufferInfo(hOutput, &screen_info);
  // 根据窗口大小计算长宽
  height = screen_info.srWindow.Bottom - screen_info.srWindow.Top + 1;
  width = screen_info.srWindow.Right - screen_info.srWindow.Left + 1;
  // 设置屏幕缓冲区的大小
  SetConsoleScreenBufferSize(hOutput, {width, height});
3.3 获取标准输入

首先获取输入句柄,同时清空之前的消息。从控制台读取一个事件记录。
注意:所有的控制台读写函数都是阻塞的,因此可以通过GetNumberOfConsoleInputEvents 获取当前输入缓冲区中的事件数。

  HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
  FlushConsoleInputBuffer(hInput);

  INPUT_RECORD record;
  common::ulong number;
  if (ReadConsoleInput(hInput, &record, 1, &number)) {
    // 判断是否键盘输入,且是按下按键操作
    if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) {
      key = record.Event.KeyEvent.wVirtualKeyCode;
    }
  }
3.4 数据输出到屏幕

清楚指定位置的屏幕,同时在指定位置打印数据。

void Clear(COORD target) {
  common::ulong length = 0;
  // 使用空格字符覆盖指定的行,长度为窗口大小
  FillConsoleOutputCharacterA(hOutput, 0x20, width + 1, target, &length);
}

void Print(const char* str, common::ulong len, COORD target) {
  common::ulong length = 0;
  // 清除旧数据
  Clear(target);
  // 从指定位置开始打印字符串
  WriteConsoleOutputCharacterA(hOutput, str, len, target, &length);
}

4 完整示例

Window 平台实现简单的进程信息查看。simple_taskmgr

你可能感兴趣的:(windows)