夏树的C语言篇 – 基于GDI的控制台贴图

夏树的C语言篇 – 修改控制台窗口大小

0.写在前面

前些阵子在完成C语言打卡任务的时候遇到了调用system函数调整控制台窗口大小的时候无法通过ReadConsoleInput监听鼠标事件的问题, 经过了一番修改, 终于将这个问题解决了。那么接下来, 夏树将介绍两个调整控制台窗口大小的方法 (ReadConsoleInput的问题在下一篇文章介绍…吧?)

1.system函数

调用system函数的方法也是网上介绍的最多的一种方法, 通过调用Windows控制台命令, 可以将当前的控制台窗口调整到自定义的行和列。
例如, 下面的例子就是将控制台窗口调整到20行30列(而最终的窗口的大小是与控制台的字体大小有关的):

system("mode con cols=30 lines=20");

2.通过调用API函数

其实通过第一部分所提到的system函数已经足以在大部分情况中都适用, 但是在少数情况在调用system函数后会使之后部分函数的功能失效, 就需要寻找其他的解决方法了, 下面将通过调用API函数进行解决。

2.1.设置屏幕缓冲区大小

首先需要明确的一点是, 控制台的窗口大小不能超过控制台屏幕缓冲区的大小(这一点也可以在控制台窗口的属性中验证, 试着分别调节窗口大小和屏幕缓冲区大小), 因此如果单独通过MoveWindowSetWindowPos函数去设置超过屏幕缓冲区大小窗口大小, 自然是不行的(至少在Win7和XP下是如此)。
Windows.h头文件中提供了SetConsoleScreenBufferSize函数, 用于设置屏幕缓冲区大小, 其函数原型为:

BOOL WINAPI SetConsoleScreenBufferSize(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwSize
);

其中, 参数hConsoleOutput为控制台屏幕缓冲区的句柄; dwSize则是一个COORD结构, 用于指定新的控制台屏幕缓冲区的大小,以字符列和行为单位。
举个例子, 将屏幕缓冲区设置为20行30列的方法如下:

HANDLE hOut;
COORD bufferSize;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
bufferSize.X = 30;
bufferSize.Y = 20;
SetConsoleScreenBufferSize(hOut, bufferSize);

2.2.设置控制台窗口大小

在设置了屏幕缓冲区大小之后, 可以使用MoveWindowSetWindowPos函数对控制台窗口大小进行调整, 以MoveWindow为例, 其函数原型为:

BOOL MoveWindow(
  HWND hWnd,
  int  X,
  int  Y,
  int  nWidth,
  int  nHeight,
  BOOL bRepaint
);

参数hWnd为当前窗口的句柄, XY代表窗口左上角点的坐标, nWidthnHeight为窗口大小, bRepaint用于指示窗口是否重绘。
举个例子, 将窗口大小设置为宽250px高100px, 并置于X:20px, Y:26px的方法如下:

TCHAR title[256];
HWND hWnd;
GetConsoleTitle(title, 256);
hWnd = FindWindow(0, title);
MoveWindow(hWnd,20,26,250,100,true);

2.3.*让窗口大小自适应屏幕缓冲区的行和列

屏幕缓冲区大小的行列尺寸是与控制台窗口显示的字体大小有关的,因此屏幕缓冲区的行和列与窗口大小的像素之间的转换经常是不固定的,为了将两者相适应,则可以通过获取控制台当前所显示的单个字体的宽高像素信息来实现相互转化。
需要用到GetCurrentConsoleFontGetConsoleFontSize这两个函数来获取当前控制台所显示是的单个字符的宽高像素, 他们的函数原型如下:

BOOL WINAPI GetCurrentConsoleFont(
  _In_  HANDLE             hConsoleOutput,
  _In_  BOOL               bMaximumWindow,
  _Out_ PCONSOLE_FONT_INFO lpConsoleCurrentFont
);
COORD WINAPI GetConsoleFontSize(
  _In_ HANDLE hConsoleOutput,
  _In_ DWORD  nFont
);

使用方法如下, 最后将字体的宽高信息存储在COORD结构的fontSize变量中:

HANDLE hOut;
CONSOLE_FONT_INFO consoleCurrentFont;
COORD fontSize;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetCurrentConsoleFont(hOut, false, &consoleCurrentFont);
fontSize = GetConsoleFontSize(hOut,consoleCurrentFont.nFont);
fontSize.X; //单个字体的宽度
fontSize.Y; //单个字体的高度

那么屏幕缓冲区的行和列与窗口大小之间的转换则: 窗口宽度 = 列数✖单个字体的宽度; 窗口高度 = 行数✖单个字体的高度 即可。
注意: 如果你使用的环境是VC6的话, GetCurrentConsoleFontGetConsoleFontSize函数在windows.h头文件中是未定义的(这两个函数位于Kernel32.lib中), 而重复定义函数在高版本的VC中又会出错, 因此可以通过判断_MSC_VER这个宏的方式来判断版本是否为VC6, 并进行兼容:

#if _MSC_VER <= 1200
#pragma comment(lib,"Kernel32.lib")
typedef struct _CONSOLE_FONT_INFO {
    DWORD nFont;
    COORD dwFontSize;
} CONSOLE_FONT_INFO, *PCONSOLE_FONT_INFO;
bool WINAPI GetCurrentConsoleFont(
    HANDLE hConsoleOutput,
    bool bMaximumWindow,
    PCONSOLE_FONT_INFO lpConsoleCurrentFont
);
COORD WINAPI GetConsoleFontSize(
    HANDLE hConsoleOutput,
    DWORD nFont
);
#endif

2.4.综合一下

至此, 已经可以将上面的步骤封装成为一个新的函数SetConsoleSize, 如下所示:

void SetConsoleSize(int x, int y, int cols, int lines)
{
    HANDLE hOut;
    CONSOLE_FONT_INFO consoleCurrentFont;
    COORD bufferSize,fontSize;
    TCHAR title[256];
    HWND hWnd;
    //Set console buffer size
    hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetCurrentConsoleFont(hOut, false, &consoleCurrentFont);
    fontSize = GetConsoleFontSize(hOut,consoleCurrentFont.nFont);
    bufferSize.X = cols;
    bufferSize.Y = lines;
    SetConsoleScreenBufferSize(hOut, bufferSize);
    //Set console window size
    GetConsoleTitle(title, 256);
    hWnd = FindWindow(0, title);
    MoveWindow(hWnd,x,y,(cols+4)*fontSize.X,(lines+2)*fontSize.Y,true);
}

此时, 调用SetConsoleSize(20, 20, 30, 30);便与system("mode con cols=30 lines=20");效果一致了, 避免了system函数对部分函数的干扰, 同时也可以设置控制台窗口的坐标。

3.一些参考

通常使用Bing搜索相关函数很容易找到与之相关的文档:

发布者

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注