Windows控制台API基本使用(上)

目录
前言
一、控制台缓冲区大小
一、获取缓冲区大小
二、设置缓冲区大小
三、更详细的结构体
四、特性
二、窗口大小
一、获取窗口大小
二、设置窗口大小
三、光标位置
一、获取光标位置
二、设置光标位置
四、设置光标是否显示
一、旧版API
二、新版控制台的 ANSI 转义序列
三、新版控制台特性
五、通过代码打开 ANSI 转义序列
六、控制台编码
一、设置输出编码
二、设置输入编码
七、修改控制台文本样式
一、使用API
二、使用ANSI转义序列
八、控制台窗口标题
一、获取控制台窗口标题
二、设置窗口标题
一、使用旧版API
二、使用转义序列
三、获取控制台原始标题
九、置顶窗口
十、移动窗口
十一、(旧API)ScrollConsoleScreenBuffer
十二、清空控制台
十三、控制台字体
一、获取控制台字体
二、设置控制台字体
三、字体无法使用
一、参数未正确设置
附录 - 后篇链接
前言

微软官方的提供代码参考文档:控制台结构 - Windows Console | Microsoft Docs


  本文章所使用的一些API为旧版API,建议使用其它方案代替它们。
  本文章多有些纰漏,若发现错误请在评论区指正。
  注: 文章内所有代码均在VS2019下编译通过。

一、控制台缓冲区大小
一、获取缓冲区大小
  GetConsoleScreenBufferInfo 可以获取到控制台窗口的一些信息。

BOOL WINAPI GetConsoleScreenBufferInfo(
  _In_  HANDLE                      hConsoleOutput,           // 输出句柄
  _Out_ PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo // 缓冲区信息结构体
);
1
2
3
4
  PCONSOLE_SCREEN_BUFFER_INFO 实际上是一个指针,可以使用_CONSOLE_SCREEN_BUFFER_INFO 或 CONSOLE_SCREEN_BUFFER_INFO 替代它。此结构体的原型为:

typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
  COORD      dwSize;              // 缓冲区大小
  COORD      dwCursorPosition;    // 控制台屏幕缓冲区中光标的列和行坐标
  WORD       wAttributes;
  SMALL_RECT srWindow;            // 显示窗口左上角和右下角的控制台屏幕缓冲区的坐标(即窗口大小)
  COORD      dwMaximumWindowSize; // 在给定当前屏幕缓冲区大小和字体和屏幕大小的情况下,它包含控制台窗口的最大大小(字符列和行)
} CONSOLE_SCREEN_BUFFER_INFO;
1
2
3
4
5
6
7
  此结构体同样包含缓冲区光标位置的信息,在获取光标位置时可以使用此结构体。
  下方代码将会输出缓冲区大小。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO info;

    GetConsoleScreenBufferInfo(outputHandle, &info);

    printf("缓冲区大小: x=%hd, y=%hd\n", info.dwSize.X, info.dwSize.Y);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

二、设置缓冲区大小
  (旧版API)SetConsoleScreenBufferSize 可以修改控制台的缓冲区大小。

WINBASEAPI
BOOL
WINAPI
SetConsoleScreenBufferSize(
    _In_ HANDLE hConsoleOutput, // 输出句柄
    _In_ COORD dwSize           // 新缓冲区大小
    );
1
2
3
4
5
6
7
  此函数接受一个 COORD 结构体 作为参数,其原型为:

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;
1
2
3
4
  下方代码将会设置缓冲区大小为115×40个字符。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 定义大小
    COORD size = { 115, 40 };

    // 应用
    SetConsoleScreenBufferSize(outputHandle, size);
    SetConsoleScreenBufferSize(outputHandle, size);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  不建议使用此API。

三、更详细的结构体
  CONSOLE_SCREEN_BUFFER_INFOEX 结构体能够记录更多的控制台信息,并且也有与之匹配的获取(GetConsoleScreenBufferInfoEx)和设置(SetConsoleScreenBufferInfoEx)函数。多数情况下,笔者更推荐使用 CONSOLE_SCREEN_BUFFER_INFOEX,因为 CONSOLE_SCREEN_BUFFER_INFOEX 拥有与之匹配的函数,而 _CONSOLE_SCREEN_BUFFER_INFO 和 CONSOLE_SCREEN_BUFFER_INFO 仅有一个获取函数(GetConsoleScreenBufferInfo),所以,能用就用吧。
  下面的代码演示了如何使用 CONSOLE_SCREEN_BUFFER_INFOEX。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFOEX info;
    info.cbSize = sizeof(info);        // 不要忘记指定结构体大小

    GetConsoleScreenBufferInfoEx(outputHandle, &info);
    printf(
        "缓冲区大小: x=%hd, y=%hd\n"
        "窗口大小: x=%hd, y=%hd\n"
        "光标坐标: x=%hd, y=%hd\n"
        , info.dwSize.X, info.dwSize.Y, info.srWindow.Right + 1, info.srWindow.Bottom + 1, info.dwCursorPosition.X, info.dwCursorPosition.Y);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


四、特性
  新版控制台又出特性了。在使用 GetConsoleScreenBufferInfoEx 获取控制台缓冲区信息后,如果再次设置,控制台的大小竟会缩减1行!?笔者在 stackoverflow 查找到了解决方法——只需在获取信息后将 srWindow 的 Right 和 Bottom 加 1。请参阅下方代码。

#include <stdio.h>
#include <Windows.h>

int main() {
    // 输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 控制台缓冲区信息
    CONSOLE_SCREEN_BUFFER_INFOEX csbi;
    csbi.cbSize = sizeof(csbi);

    // 获取信息
    GetConsoleScreenBufferInfoEx(outputHandle, &csbi);
    printf("获取到的 srWindow 的值:{%d %d %d %d}\n", csbi.srWindow.Left, csbi.srWindow.Top, csbi.srWindow.Right, csbi.srWindow.Bottom);
    csbi.srWindow.Right++;
    csbi.srWindow.Bottom++;
    printf("更正后的 srWindow 的值:{%d %d %d %d}\n", csbi.srWindow.Left, csbi.srWindow.Top, csbi.srWindow.Right, csbi.srWindow.Bottom);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  效果:


二、窗口大小
一、获取窗口大小
  对于这种需求,CONSOLE_SCREEN_BUFFER_INFOEX 可以解决一切问题。但现在这里将依然使用 GetConsoleScreenBufferInfo 函数获取窗口大小。下面代码将会演示此功能。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO info;

    GetConsoleScreenBufferInfo(outputHandle, &info);

    printf("窗口大小: x=%hd, y=%hd\n", info.srWindow.Right + 1, info.srWindow.Bottom + 1);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  注: srWindow会把0也算进窗口大小内,所以需要将结果加1得到正确的数字。
  使用 CONSOLE_SCREEN_BUFFER_INFOEX 的样例在第一节的第三小节。

二、设置窗口大小
  (旧版API)SetConsoleWindowInfo 可以做到这一点。
  查阅 Microsoft Docs 了解此函数的参数。
  下面的代码可以实现此功能。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    SMALL_RECT winSize = {0, 0};

    winSize.Right = 20 - 1;
    winSize.Bottom = 10 - 1;

    SetConsoleWindowInfo(outputHandle, 1, &winSize);
    SetConsoleWindowInfo(outputHandle, 1, &winSize);

    printf("窗口大小被改变。\n");

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


三、光标位置
一、获取光标位置
  第一节的第三小节的示例代码演示了如何获取光标位置。而 _CONSOLE_SCREEN_BUFFER_INFO 和 CONSOLE_SCREEN_BUFFER_INFO 也可以获取光标位置。
  _CONSOLE_SCREEN_BUFFER_INFO 和 CONSOLE_SCREEN_BUFFER_INFO 的 dwCursorPosition 成员规定了光标的坐标。 CONSOLE_SCREEN_BUFFER_INFOEX 也有该成员。下面代码演示了如何使用 _CONSOLE_SCREEN_BUFFER_INFO 和 CONSOLE_SCREEN_BUFFER_INFO 的 dwCursorPosition 成员获取光标信息。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(outputHandle, &info);

    printf("光标位置: (%hd,%hd)\n", info.dwCursorPosition.X, info.dwCursorPosition.Y);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14


二、设置光标位置
  Windows 10 支持特殊 ANSI 序列,可以直接输出 ANSI 转义序列移动光标。

#include <stdio.h>

int main(int argc, char* argv[]) {

    //    光标即将前往的坐标
    short x = 5, y = 5;
    printf("\x1b[%hd;%hdH", y + 1, x);
    printf("Hello world!\n");

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11

  使用 (旧版API)SetConsoleCursorPosition 函数可以在新旧版控制台中工作。如果程序需要跨平台,最好不使用此 API。


四、设置光标是否显示
一、旧版API
  (旧版API)GetConsoleCursorInfo、(旧版API)SetConsoleCursorInfo 和 (旧版结构)CONSOLE_CURSOR_INFO 搭配可以设置光标信息。下方代码演示了这项工作。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 获取句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 获取光标信息
    _CONSOLE_CURSOR_INFO cursorInfomations;
    GetConsoleCursorInfo(outputHandle, &cursorInfomations);
    // 将光标设为不可见
    cursorInfomations.bVisible = 0;
    // 应用光标信息
    SetConsoleCursorInfo(outputHandle, cursorInfomations);

    printf("光标被 API 隐藏了。\n");

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

  此 API 建议在旧版控制台使用,最好不在新版控制台用。

二、新版控制台的 ANSI 转义序列
  下方代码将通过 ANSI 转义序列显示或隐藏光标。

#include <stdio.h>
#include <Windows.h>

void setCursorVisible(bool status) {
    // \033[?25h  显示光标
    // \033[?25l  隐藏光标
    printf("\033[?25%c", (status ? 'h' : 'l'));
}

int main(int argc, char* argv[]) {
    setCursorVisible(1); // 显示
    setCursorVisible(0); // 隐藏

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
三、新版控制台特性
  在 Win10 新版控制台中最好不要隐藏光标,因为不管使用 API 隐藏还是 ANSI 转义序列隐藏,用鼠标拖拽窗口改变大小都会导致光标重新显示。下方GIF演示了新版控制台的特性。


五、通过代码打开 ANSI 转义序列
  Windows 10 新版控制台可以手动开启 ANSI 转义序列,下方代码将会演示此功能,重点在于enabledANSI函数。

#include <stdio.h>
#include <Windows.h>

//   启用 ANSI
bool enabledANSI(HANDLE outputHandle) {
    if (outputHandle == INVALID_HANDLE_VALUE) { return 0; }

    DWORD dwMode_Out = 0;
    // 获取当前控制台模式
    if (!GetConsoleMode(outputHandle, &dwMode_Out)) { return 0; }

    // 启用设置
    dwMode_Out |= ENABLE_PROCESSED_OUTPUT; dwMode_Out |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    dwMode_Out |= DISABLE_NEWLINE_AUTO_RETURN;

    // 设置
    if (!SetConsoleMode(outputHandle, dwMode_Out)) { return 0; }

    return 1;
}

int main(int argc, char* argv[]) {
    bool status = enabledANSI(GetStdHandle(STD_OUTPUT_HANDLE));

    printf("ANSI 转义序列打开....。\b\b\b\b\b\b");

    if (status) {
        printf("成功");
    } else {
        printf("失败");
    }
    printf("\n");

    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
  此程序在笔者的系统上成功运行。


六、控制台编码
一、设置输出编码
  SetConsoleOutputCP 函数可以修改当前控制台输出的代码页,其接受一个 UINT(unsigned int) 作为参数。

BOOL WINAPI SetConsoleOutputCP(
  _In_ UINT wCodePageID       // 新代码页
);
1
2
3
  下面的代码将会演示此函数的功能。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    SetConsoleOutputCP(437);

    printf("English.\n");
    printf("中文。\n");
    printf("日本語。\n");

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

  代码页437是美国英语的代码页,其只能显示英语字符(准确来说是 ASCII 码内的字符),所以在输出其它语种时就会出现乱码,如上图所示。
  下表是部分代码页。

代码页    描述
437    美国英语
850    多语言
857    土耳其语
866    俄语
869    现代希腊语
936    简体中文
  定义常量以区分这些代码页。

//C
#define US           437
#define MULTILINGUAL 850
#define TURKISH      857
#define RUSSIA       866
#define MODERNGREEK  869
#define CHINESE      936
//C++
unsigned short const US           = 437,
                     MULTILINGUAL = 850,
                     TURKISH      = 857,
                     RUSSIA       = 866,
                     MODERNGREEK  = 869,
                     CHINESE      = 936;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  GetConsoleOutputCP 可以获取当前活动代码页。下方代码将会演示此功能。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    printf("Code: %hd\n", GetConsoleOutputCP());

    return 0;
}
1
2
3
4
5
6
7
8


二、设置输入编码
  SetConsoleCP 函数可以设置输入编码。Microsoft Docs 这么描述它:

“设置与调用进程关联的控制台使用的输入代码页。 控制台使用其输入代码页将键盘输入转换为相应的字符值。”

  Microsoft Docs 明确说明了此 API 用于设置输入的字符的代码页。下方代码将会演示此功能。

// VS2019下需要定义此宏
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 修改输入代码页
    SetConsoleCP(437);

    printf("输入一串字符串: ");
    char getString[20] = "";
    scanf("%[^\n]", getString);

    printf("输入的内容是:\n%s\n", getString);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  虽然控制台在接受字符串时显示了中文,但是在输出该字符串时却出现了乱码,而控制台还是可以输出中文,这就是 SetConsoleCP 的作用,更改输入内容的代码页。
  与 GetConsoleOutputCP 一样,GetConsoleCP 也能获取活动的代码页,但是获取的是活动的输入代码页的代码。

七、修改控制台文本样式
一、使用API
  SetConsoleTextAttribute 函数可以更改接下来输出的文字的样式,通过下方代码您就可以了解其作用。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    SetConsoleTextAttribute(outputHandle, 0xf0);
    printf("白底黑字\n");

    SetConsoleTextAttribute(outputHandle, 0x4e);
    printf("红底黄字\n");

    SetConsoleTextAttribute(outputHandle, 0x1a);
    printf("蓝底绿字\n");
    
    SetConsoleTextAttribute(outputHandle, 0x801a);
    printf("蓝底绿字 + 下划线\n");

    SetConsoleTextAttribute(outputHandle, 0x4e | COMMON_LVB_UNDERSCORE);
    printf("红底黄字 + 下划线\n");

    SetConsoleTextAttribute(outputHandle, 0x07);
    printf("黑底灰字\n");

    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

  此 API 在新旧版控制台均能工作。
  Windows 还提供了其它的样式。

下方内容摘自 consoleapi2.h

//
// Attributes flags:
//

#define FOREGROUND_BLUE      0x0001 // text color contains blue.
#define FOREGROUND_GREEN     0x0002 // text color contains green.
#define FOREGROUND_RED       0x0004 // text color contains red.
#define FOREGROUND_INTENSITY 0x0008 // text color is intensified.
#define BACKGROUND_BLUE      0x0010 // background color contains blue.
#define BACKGROUND_GREEN     0x0020 // background color contains green.
#define BACKGROUND_RED       0x0040 // background color contains red.
#define BACKGROUND_INTENSITY 0x0080 // background color is intensified.
#define COMMON_LVB_LEADING_BYTE    0x0100 // Leading Byte of DBCS
#define COMMON_LVB_TRAILING_BYTE   0x0200 // Trailing Byte of DBCS
#define COMMON_LVB_GRID_HORIZONTAL 0x0400 // DBCS: Grid attribute: top horizontal.
#define COMMON_LVB_GRID_LVERTICAL  0x0800 // DBCS: Grid attribute: left vertical.
#define COMMON_LVB_GRID_RVERTICAL  0x1000 // DBCS: Grid attribute: right vertical.
#define COMMON_LVB_REVERSE_VIDEO   0x4000 // DBCS: Reverse fore/back ground attribute.
#define COMMON_LVB_UNDERSCORE      0x8000 // DBCS: Underscore.

#define COMMON_LVB_SBCSDBCS        0x0300 // SBCS or DBCS flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  在 cmd 中使用 “color /?” 命令可以查看十六进制表示下颜色的排列,方法可在 【C++】Windows控制台API基本使用(下) 查阅。

二、使用ANSI转义序列
  下方代码将会演示使用 ANSI 转义序列输出多样式文本。

#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("\x1b[31mText\x1b[0m\n");
    printf("\x1b[31;1mText\x1b[0m\n");
    printf("\x1b[31;1;47;4mText\x1b[0m\n");
    printf("\x1b[31;1;7mText(反色)\x1b[0m\n");

    return 0;
}
1
2
3
4
5
6
7
8
9
10

  这种写法在旧版控制台无效。

八、控制台窗口标题
一、获取控制台窗口标题
  GetConsoleTitle 函数可以获取窗口的标题。其原型为:

WINBASEAPI DWORD WINAPI GetConsoleTitleA(
    _Out_writes_(nSize) LPSTR lpConsoleTitle,
    _In_ DWORD nSize
    );

WINBASEAPI DWORD WINAPI GetConsoleTitleW(
    _Out_writes_(nSize) LPWSTR lpConsoleTitle,
    _In_ DWORD nSize
    );
#ifdef  UNICODE
#define GetConsoleTitle  GetConsoleTitleW
#else
#define GetConsoleTitle  GetConsoleTitleA
#endif // !UNICODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  上方截取了 consoleapi2.h 中有关 (旧API)GetConsoleTitle 的部分。在宏 UNICODE 存在时,GetConsoleTitle 为 GetConsoleTitlteW,否则为 GetConsoleTitleA。GetConsoleTitleW 是 GetConsoleTitle 的宽字符版,而 GetConsoleTitleA 是摘字符版。在源文件中取消宏 UNICODE 就能使用 ANSI 版。下面的代码演示了如何正确使用 GetConsoleTitleA 和 GetConsoleTitleW。

#include <stdio.h>
#include <wchar.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    char    titleA[30]    =  "";
    wchar_t titleW[30]    = L"";

    GetConsoleTitle (titleW, 30);
    GetConsoleTitleA(titleA, 30);

    printf("titleA = %s\n"
           "titleW = %ls\n",
           titleA, titleW);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  Windows API 定义了不同的字符串类型,下方截取了 GetConsoleTitle 及其宽字符和窄字符版所用到的类型的定义。

// 截取自 Winnt.h
typedef _Null_terminated_ CHAR *NPSTR, *LPSTR, *PSTR;
typedef _Null_terminated_ WCHAR *NWPSTR, *LPWSTR, *PWSTR;
1
2
3
二、设置窗口标题
一、使用旧版API
  (旧API)SetConsoleTitle 可以设置窗口标题,与 (旧API)GetConsoleTitle 一样,它也分宽字符和窄字符两种版本。下方代码演示了如何使用这些 API。

#include <stdio.h>
#include <wchar.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    char    titleA[] =  "titleA";
    wchar_t titleW[] = L"titleW";

    SetConsoleTitleA(titleA);
    printf("TitleA.\n");
    getchar();

    // 或在宏 UNICODE 存在时使用 SetConsoleTitle(titleW);
    SetConsoleTitleW(titleW);
    printf("TitleW.\n");


    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


二、使用转义序列
  Windows 新版控制台支持使用转义序列控制控制台窗口标题,其格式为:

\033]0;标题字符串\007
1
  转义序列的标题使用响铃字符(ASCII: 7)为结尾,下方代码演示了此转义序列。

#include <stdio.h>

int main() {
    printf("\033]0;新标题\007");

    return 0;
}
1
2
3
4
5
6
7


三、获取控制台原始标题
  (旧API)GetConsoleOriginalTitle 函数可以获取控制台的原始标题。此函数也分宽字符和窄字符两种版本,其参数与 (旧API)GetConsoleTitle 的参数一样。下面的程序将会演示其功能。

#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[]) {
    using std::cout;

    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 设置新标题
    SetConsoleTitleA("新标题");

    char originalTitle[50] = "";
    GetConsoleOriginalTitleA(originalTitle, 50);

    cout << "原始标题: " << originalTitle << std::endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

  (旧API)GetConsoleOriginalTitle 在 (旧API)SetConsoleTitle 后调用,但 (旧API)GetConsoleOriginalTitle 获取的标题不是现在的新标题,而是开始的标题。

九、置顶窗口
  SetWindowPos 函数可以实现此功能。其原型为:

// 摘自 WinUser.h
WINUSERAPI BOOL WINAPI SetWindowPos(
    _In_ HWND hWnd,                // 要操作的窗口
    _In_opt_ HWND hWndInsertAfter,
    _In_ int X,                    // 窗口新的位置的X轴
    _In_ int Y,                    // 窗口新的位置的Y轴
    _In_ int cx,                   // 窗口的新宽度
    _In_ int cy,                   // 窗口的新高度
    _In_ UINT uFlags               // 附加属性;
1
2
3
4
5
6
7
8
9
  uFlags将决定 X、Y、cx 和 cy 四个参数是否生效。下方代码演示了如何置顶和不置顶窗口。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    //   获取控制台窗口的句柄
    HWND window = GetConsoleWindow();

    // 置顶窗口
    SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

    // 不置顶窗口
    SetWindowPos(window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

    /*
    SetWindowPos 函数的 uFlags 参数如果包括 SWP_NOMOVE,则 X 和 Y 参数不生效,窗口就不会移动。
    如果包含 SWP_NOSIZE,则窗口大小不会被重新定义。
    */
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  上述代码中 window 是一个控制台窗口对象(或窗口句柄),GetConsoleWindow 将会返回控制台窗口的句柄。调用 SetWindowPos 时传递了控制台窗口的句柄,则接下来要操作的就是控制台窗口。
  与 GetConsoleWindow 的 API 是 GetForegroundWindow 函数,它也能返回窗口句柄,但其返回的是当前焦点所在的窗口的句柄。

  上图中正在被操作的窗口就是当前焦点所在窗口。
  下方程序演示了置顶当前焦点所在窗口。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    //   获取当前焦点所在窗口的句柄
    HWND window = GetForegroundWindow();

    // 置顶窗口
    SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
十、移动窗口
  SetWindowPos 依然能够实现此功能。刚才讲过,若 uFlags 包含 SWP_NOMOVE 则指定窗口不会移动,忽略参数 X 和 Y,如果去掉就可以实现移动窗口。下方代码演示了此功能。

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    //   获取控制台窗口
    HWND window = GetConsoleWindow();

    // 获取到的坐标
    unsigned int getPos[2] = {0};

    while (1) {
        // 获取 X 轴
        printf("X:");
        scanf("%ud", &getPos[0]);

        // 获取 Y 轴
        printf("Y:");
        scanf("%ud", &getPos[1]);

        getchar();

        // 移动窗口
        SetWindowPos(window, NULL, getPos[0], getPos[1], 0, 0, SWP_NOSIZE);

        printf("\n");
    }

    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


十一、(旧API)ScrollConsoleScreenBuffer
  (旧API)ScrollConsoleScreenBuffer 函数可以移动控制台指定区域的文本到制定区域,其原型为:

// 摘自 Microsoft Docs
BOOL WINAPI ScrollConsoleScreenBuffer(
  _In_           HANDLE     hConsoleOutput,
  _In_     const SMALL_RECT *lpScrollRectangle,
  _In_opt_ const SMALL_RECT *lpClipRectangle,
  _In_           COORD      dwDestinationOrigin,
  _In_     const CHAR_INFO  *lpFill
);
1
2
3
4
5
6
7
8
Microsoft Docs 这么描述:
参数
hConsoleOutput[in]
控制台屏幕缓冲区的句柄。 句柄必须具有 GENERIC_READ 访问权限。 有关详细信息,请参阅控制台缓冲区安全性和访问权限。

lpScrollRectangle [in]
指向 SMALL_RECT 结构的指针,该结构的成员指定要移动的控制台屏幕缓冲区矩形的左上角和右下角坐标。

lpClipRectangle [in,可选]
指向 SMALL_RECT 结构的指针,该结构的成员指定受滚动影响的控制台屏幕缓冲区矩形的左上角和右下角坐标。 此指针可以为 NULL。

dwDestinationOrigin [in]
指定lpScrollRectangle内容的新位置的左上角的oozie.coord.application.path结构,以字符为字符。

lpFill [in]
一个指向 CHAR_INFO 结构的指针,该结构指定用于填充 lpScrollRectangle 和 lpClipRectangle 的交集中的单元格的字符和颜色属性,这些单元格在移动后留空。

  此 API 的更多信息请访问 【C++】Windows控制台API基本使用(下) 有关移动控制台文本的章节。

十二、清空控制台
  Microsoft Docs 提供了 Cmd 中 cls 指令清除控制台的代码。

摘自 Microsoft Docs

#include <windows.h>

void cls(HANDLE hConsole)
{
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   SMALL_RECT scrollRect;
   COORD scrollTarget;
   CHAR_INFO fill;

   // Get the number of character cells in the current buffer.
   if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
   {
       return;
   }

   // Scroll the rectangle of the entire buffer.
   scrollRect.Left = 0;
   scrollRect.Top = 0;
   scrollRect.Right = csbi.dwSize.X;
   scrollRect.Bottom = csbi.dwSize.Y;

   // Scroll it upwards off the top of the buffer with a magnitude of the entire height.
   scrollTarget.X = 0;
   scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);

   // Fill with empty spaces with the buffer's default text attribute.
   fill.Char.UnicodeChar = TEXT(' ');
   fill.Attributes = csbi.wAttributes;

   // Do the scroll
   ScrollConsoleScreenBuffer(hConsole, &scrollRect, NULL, scrollTarget, &fill);

   // Move the cursor to the top left corner too.
   csbi.dwCursorPosition.X = 0;
   csbi.dwCursorPosition.Y = 0;

   SetConsoleCursorPosition(hConsole, csbi.dwCursorPosition);
}

int main(void)
{
   HANDLE hStdout;

   hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

   cls(hStdout);

   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
37
38
39
40
41
42
43
44
45
46
47
48
49
十三、控制台字体
一、获取控制台字体
  若要更改或获取控制台字体,应在代码中添加下方代码块:

struct CONSOLE_FONT
{
    DWORD index;
    COORD dim;
};
typedef BOOL(WINAPI* PROCSETCONSOLEFONT)(HANDLE, DWORD);
typedef BOOL(WINAPI* PROCGETCONSOLEFONTINFO)(HANDLE, BOOL, DWORD, CONSOLE_FONT*);
typedef COORD(WINAPI* PROCGETCONSOLEFONTSIZE)(HANDLE, DWORD);
typedef DWORD(WINAPI* PROCGETNUMBEROFCONSOLEFONTS)();
typedef BOOL(WINAPI* PROCGETCURRENTCONSOLEFONT)(HANDLE, BOOL, CONSOLE_FONT*);
PROCSETCONSOLEFONT SetConsoleFont;
PROCGETCONSOLEFONTINFO GetConsoleFontInfo;
PROCGETNUMBEROFCONSOLEFONTS GetNumberOfConsoleFonts;

typedef BOOL(WINAPI* PROCGETCONSOLEDISPLAYMODE)(LPDWORD);
typedef BOOL(WINAPI* PROCSETCONSOLEDISPLAYMODE)(HANDLE, DWORD, LPDWORD);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  例如:

#include <Windows.h>
#include <stdio.h>
struct CONSOLE_FONT
{
    DWORD index;
    COORD dim;
};
typedef BOOL(WINAPI* PROCSETCONSOLEFONT)(HANDLE, DWORD);
typedef BOOL(WINAPI* PROCGETCONSOLEFONTINFO)(HANDLE, BOOL, DWORD, CONSOLE_FONT*);
typedef COORD(WINAPI* PROCGETCONSOLEFONTSIZE)(HANDLE, DWORD);
typedef DWORD(WINAPI* PROCGETNUMBEROFCONSOLEFONTS)();
typedef BOOL(WINAPI* PROCGETCURRENTCONSOLEFONT)(HANDLE, BOOL, CONSOLE_FONT*);
PROCSETCONSOLEFONT SetConsoleFont;
PROCGETCONSOLEFONTINFO GetConsoleFontInfo;
PROCGETNUMBEROFCONSOLEFONTS GetNumberOfConsoleFonts;

typedef BOOL(WINAPI* PROCGETCONSOLEDISPLAYMODE)(LPDWORD);
typedef BOOL(WINAPI* PROCSETCONSOLEDISPLAYMODE)(HANDLE, DWORD, LPDWORD);

int main(int argc, char* argv[]) {
    printf("Hello world!\n");
    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
  GetCurrentConsoleFontEx 函数可以获取字体信息,其原型为:

WINBASEAPI BOOL WINAPI GetCurrentConsoleFontEx(
    _In_ HANDLE hConsoleOutput,                        // 输出句柄。
    _In_ BOOL bMaximumWindow,                          // 如果此参数为 TRUE,则为最大窗口大小检索字体信息。 如果此参数为 FALSE,则将检索当前窗口大小的字体信息。
    _Out_ PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx  // 字体结构体
    );
1
2
3
4
5
  参数 lpConsoleCurrentFontEx 的类型 PCONSOLE_FONT_INFOEX 是一个指向 CONSOLE_FONT_INFOEX 的指针,CONSOLE_FONT_INFOEX 的定义为:

typedef struct _CONSOLE_FONT_INFOEX {
    ULONG cbSize;                // 结构体的大小(以字节为单位)。
    DWORD nFont;                 // 系统控制台字体表中字体的索引。
    COORD dwFontSize;            // 字体大小,规定了字体的长与宽。
    UINT FontFamily;             // 字体间距和系列。
    UINT FontWeight;             // 字体粗细。权重的范围可以是 100 到 1000,以 100 的倍数表示。
    WCHAR FaceName[LF_FACESIZE]; // 字体名称。
} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
1
2
3
4
5
6
7
8
  在调用 GetCurrentConsoleFontEx 前必须设置 cbSize 的值,否则函数调用失败。其值可以设为 sizeof(CONSOLE_FONT_INFOEX),下方代码将会演示如何使用此函数和结构。

#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[]) {
    using std::cout;
    using std::wcout;

    // 控制台字体对象
    CONSOLE_FONT_INFOEX cfi;
    cfi.cbSize = sizeof(cfi);
    // 输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 获取字体信息
    GetCurrentConsoleFontEx(outputHandle, 0, &cfi);

    // 输出字体信息
    wcout << "字体: " << cfi.FaceName;
    cout << std::endl;
    cout << "大小: " << cfi.dwFontSize.X << "*" << cfi.dwFontSize.Y << std::endl
         << "权重: " << cfi.FontWeight << std::endl;

    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

  不知道为什么,无法获取到控制台字体。

二、设置控制台字体
  (旧版API)SetCurrentConsoleFontEx 函数可以修改字体,需要搭配 (旧版结构)CONSOLE_FONT_INFOEX 结构体使用。下方的程序将会演示此功能。

/*
  此程序将会演示如何使用 SetCurrentConsoleFontEx 函数更改控制台字体为“楷体”。
  有一点需要注意,更改的字体必须是等宽字体,不是等宽字体的修改后都会变成默认字体。
*/
#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 控制台字体对象
    CONSOLE_FONT_INFOEX cfi;
    cfi.cbSize = sizeof(cfi);       // 不要忘记设置结构体的大小
    // 输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    printf("字体会在按下回车后更改。\n");
    getchar();

    // 更改字体
    cfi.nFont        = 0;
    cfi.dwFontSize.X = 8;           // 更改字体宽度
    cfi.dwFontSize.Y = 18;          // 更改字体高度
    cfi.FontFamily   = FF_DONTCARE;
    cfi.FontWeight   = FW_NORMAL;
    wcscpy_s(cfi.FaceName, L"黑体"); // 更改字体名
    SetCurrentConsoleFontEx(outputHandle, FALSE, &cfi); // 应用更改

    printf("更改完毕。\n");

    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


三、字体无法使用
  某些字体可能无法被应用,这可能是由以下原因引发的:
  1. 参数未正确设置。
  2. 字体不支持当前代码页。

一、参数未正确设置
  请检查代码中是否正确设置控制台字体对象的 cbSize 成员,如果有,那就是因为其它项没有正确设置。
  程序需要设置所使用字体的各种信息,如权重、名称、大小等,如果您不知道如何设置它们,可以考虑从当前设置中设置。也就是在使用 (旧版API)SetCurrentConsoleFontEx 前先调用 GetCurrentConsoleFontEx,下面是上方代码的改进。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[]) {
    // 控制台字体对象
    CONSOLE_FONT_INFOEX cfi;
    cfi.cbSize = sizeof(cfi);       // 不要忘记设置结构体的大小
    // 输出句柄
    HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    // 获取字体
    GetCurrentConsoleFontEx(outputHandle, 0, &cfi);

    printf("字体会在按下回车后更改。\n");
    getchar();

    // 更改字体
    cfi.dwFontSize.X = 8;           // 更改字体宽度
    cfi.dwFontSize.Y = 18;          // 更改字体高度
    cfi.FontFamily   = FF_DONTCARE;
    cfi.FontWeight   = FW_NORMAL;
    wcscpy_s(cfi.FaceName, L"Sarasa Fixed Slab SC Semibold"); // 更改字体名
    SetCurrentConsoleFontEx(outputHandle, FALSE, &cfi); // 应用更改

    printf("更改完毕。\n");

    return 0;
}
————————————————
版权声明:本文为CSDN博主「Takanawa-door」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48178135/article/details/121306169

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值