控制台窗口界面编程控制
〇、摘要
一、概述
二、控制台文本窗口的一般控制步骤
三、控制台窗口操作
四、文本属性操作
五、文本输出
六、文本操作示例
七、滚动和移动
八、光标操作
九、读取键盘信息
十、读取鼠标信息
十一、结语
补充篇--经典程序(Internet 资源)
摘要:
文本界面的控制台应用程序开发是深入学习 C++、掌握交互系统的实现方法的最简单的
一种手段。然而,Visual C++的 C++专用库却没有 TC 所支持的文本(字符)屏幕控制函数,
为此本系列文章从一般控制步骤、控制台窗口操作、文本(字符)控制、滚动和移动光标、键
盘和鼠标等几个方面讨论控制台窗口界面的编程控制方法。
在众多 C++开发工具中,由于 Microsoft 本身的独特优势,选用 Visual C++已越来越
被众多学习者所接受。显然,现今如果还再把 TC 作为开发环境的话,不仅没有必要,而且
也不利于向 Windows 应用程序开发的过渡。然而,Visual C++的 C++专用库却没有 TC 所支
持的文本屏幕(控制台窗口)控制函数(相应的头文件是 conio.h)。这必然给 C++学习者在文
本界面设计和编程上带来诸多不便。要知道,文本界面设计是一种深入学习 C++、掌握交互
系统的实现方法的最简单的一种手段,它不像 C++的 Windows 图形界面应用 程序,涉及知
识过多。为此,本系列文章来讨论在 Visual C++ 6.0 开发环境中,如何编写具有美观清晰
的控制台窗口界面的 C++应用程序。
(一)概述操作
所谓控制台应用程序,就是指那些需要与传统 DOS 操作系统保持某种程序的兼容,同时
又不需要为用户提供完善界面的程序。简单地讲,就是指在 Windows 环境下运行的 DOS 程序。
一旦控制台应用程序在 Windows 操作系统中运行后,就会弹出一个窗口。例如下列代码:
#include
int main(int argc,char *argv[])
{
printf("Hello, Console!\n");
return 0;
}
单击小型编译工具栏中的“Build”按钮或按 F7 键,系统出现一个对话框,询问是否将
此项目的工作文件夹设定源文件所在的文件夹,单击[是]按钮,系统开始编译。 单击小型
编译工具栏中的“Execute Program”按钮或按 Ctrl+F5 键,运行刚才的程序。 程序运行后,
弹出下图的窗口:
这就是控制台窗口,与传统的 DOS 屏幕窗口相比最主要的区别有:
(1) 默认的控制台窗口有系统菜单和标题,它是一个内存缓冲区窗口,缓冲区大小取决
于 Windows 操作系统的分配;而 DOS 屏幕是一种物理窗口,不具有 Windows 窗口特性,其大
小取决于 ROM BIOS 分配的内存空间。
(2) 控制台窗口的文本操作是调用低层的 Win32 APIs,而 DOS 屏幕的文本操作是通过
调用 BIOS 的 16(10h)中断而实现的。
(3) 默认的控制台窗口可以接收键盘和鼠标的输入信息,设备驱动由 Windows 管理,而
DOS 屏幕窗口接收鼠标时需要调用 33h 中断,且鼠标设备驱动程序由自己安装。
在 Visual C++ 6.0 中,控制台窗口界面的一般编程控制步骤如下:调用 GetStdHandle
(二)控制台文本窗口的一般控制步骤
获取当前的标准输入(STDIN)和标准输出(STDOUT)设备句柄。函数原型为:
HANDLE GetStdHandle( DWORD nStdHandle );
其中,nStdHandle 可以是 STD_INPUT_HANDLE(标准输入设备句柄)、
STD_OUTPUT_HANDLE(标准输出设备句柄)和 STD_ERROR_HANDLE(标准错误句柄)。
需要说明的是,“句柄”是 Windows 最常用的概念。它通常用来标识 Windows 资源(如
菜单、 图标、窗口等)和设备等对象。虽然可以把句柄理解为是一个指针变量类型,但它不
是对象所在的地址指针,而是作为 Windows 系统内部表的索引值来使用 的。调用相关文本
界面控制的 API 函数。这些函数可分为三类。一是用于控制台窗口操作的函数(包括窗口的
缓冲区大小、窗口前景字符和背景颜色、窗口标题、大小和位置等);二是用于控制台输入
输出的函数(包括字符属性操作函数);其他的函数并为最后一类。 调用 CloseHandle()来
关闭输入输出句柄。 注意,在程序中还必须包含头文件 windows.h。下面看一个程序:
#include
#include
#include
int main(void)
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 存储窗口信息
COORD pos = {0, 0};
// 获取标准输出设备句柄
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
// 获取窗口信息
GetConsoleScreenBufferInfo(hOut, &bInfo );
printf("\n\nThe soul selects her own society\n");
printf("Then shuts the door\n");
printf("On her devine majority\n");
printf("Obtrude no more\n\n");
_getch();
// 向窗口中填充字符以获得清屏的效果
FillConsoleOutputCharacter(hOut,' ', bInfo.dwSize.X * bInfo.dwSize.Y, pos,
NULL);
// 关闭标准输出设备句柄
CloseHandle(hOut);
return 0;
}
程序中,COORD 和 CONSOLE_SCREEN_BUFFER_ INFO 是 wincon.h 定义的控制台结构体类
型,其原型如下:
// 坐标结构体
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 ;
还需要说明的是,虽然在 C++中,iostream.h 定义了 cin 和 cout 的标准输入和输出流
对象。但它们只能实现基本的输入输出 操作,对于控制台窗口界面的控制却无能为力,而
且不能与 stdio.h 和 conio.h 友好相处,因为 iostream.h 和它们是 C++两套不同的输入 输
出操作方式,使用时要特别注意。
(三)控制台窗口操作操作
用于控制台窗口操作的 API 函数如下:
GetConsoleScreenBufferInfo 获取控制台窗口信息
GetConsoleTitle 获取控制台窗口标题
ScrollConsoleScreenBuffer 在缓冲区中移动数据块
SetConsoleScreenBufferSize 更改指定缓冲区大小
SetConsoleTitle 设置控制台窗口标题
SetConsoleWindowInfo 设置控制台窗口信息
此外,还有窗口字体、显示模式等控制函数,这里不再细说。下列举一个示例,程序如
下:
#include
#include
#include
int main(void)
{
char strTitle[255];
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口缓冲区信息
COORD size = {80, 25};
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
GetConsoleScreenBufferInfo(hOut, &bInfo ); // 获取窗口缓冲区信息
GetConsoleTitle(strTitle, 255); // 获取窗口标题
printf("当前窗口标题是:\n%s\n", strTitle);
_getch();
SetConsoleTitle("控制台窗口操作"); // 设置窗口标题
GetConsoleTitle(strTitle, 255);
printf("当前窗口标题是:\n%s\n", strTitle);
_getch();
SetConsoleScreenBufferSize(hOut,size); // 重新设置缓冲区大小
_getch();
SMALL_RECT rc = {0,0, 80-1, 25-1}; // 重置窗口位置和大小
SetConsoleWindowInfo(hOut,true ,&rc);
CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}
需要说明的是,控制台窗口的原点坐标是(0, 0),而最大的坐标是缓冲区大小减 1,例
如当缓冲区大小为 80*25 时,其最大的坐标是(79, 24)。
(四)文本属性操作操作
与 DOS 字符相似,控制台窗口中的字符也有相应的属性。这些属性分为:文本的前景色、
背景色和双字节字符集(DBCS)属性三种。事实上,我们最关心是文本颜色,这样可以构造出
美观的界面。颜色属性都是一些预定义标识:
FOREGROUND_BLUE 蓝色
FOREGROUND_GREEN 绿色
FOREGROUND_RED 红色
FOREGROUND_INTENSITY 加强
BACKGROUND_BLUE 蓝色背景
BACKGROUND_GREEN 绿色背景
BACKGROUND_RED 红色背景
BACKGROUND_INTENSITY 背景色加强
COMMON_LVB_REVERSE_VIDEO 反色
与文本属性相关的主要函数有:
BOOL FillConsoleOutputAttribute( // 填充字符属性
HANDLE hConsoleOutput, // 句柄
WORD wAttribute, // 文本属性
DWORD nLength, // 个数
COORD dwWriteCoord, // 开始位置
LPDWORD lpNumberOfAttrsWritten // 返回填充的个数
);
BOOL SetConsoleTextAttribute( // 设置 WriteConsole 等函数的字符属性
HANDLE hConsoleOutput, // 句柄
WORD wAttributes // 文本属性
);
BOOL WriteConsoleOutputAttribute( // 在指定位置处写属性
HANDLE hConsoleOutput, // 句柄
CONST WORD *lpAttribute, // 属性
DWORD nLength, // 个数
COORD dwWriteCoord, // 起始位置
LPDWORD lpNumberOfAttrsWritten // 已写个数
);
另外,获取当前控制台窗口的文本属性是通过调用函数 GetConsoleScreenBufferInfo
后,在 CONSOLE_SCREEN_ BUFFER_INFO 结构成员 wAttributes 中得到。
(五)文本输出
操作文本输出函数有:
BOOL FillConsoleOutputCharacter( // 填充指定数据的字符
HANDLE hConsoleOutput, // 句柄
TCHAR cCharacter, // 字符
DWORD nLength, // 字符个数
COORD dwWriteCoord, // 起始位置
LPDWORD lpNumberOfCharsWritten // 已写个数
);
BOOL WriteConsole( // 在当前光标位置处插入指定数量的字符
HANDLE hConsoleOutput, // 句柄
CONST VOID *lpBuffer, // 字符串
DWORD nNumberOfCharsToWrite, // 字符个数
LPDWORD lpNumberOfCharsWritten, // 已写个数
LPVOID lpReserved // 保留
);
BOOL WriteConsoleOutput( // 向指定区域写带属性的字符
HANDLE hConsoleOutput, // 句柄
CONST CHAR_INFO *lpBuffer, // 字符数据区
COORD dwBufferSize, // 数据区大小
COORD dwBufferCoord, // 起始坐标
PSMALL_RECT lpWriteRegion // 要写的区域
);
BOOL WriteConsoleOutputCharacter( // 在指定位置处插入指定数量的字符
HANDLE hConsoleOutput, // 句柄
LPCTSTR lpCharacter, // 字符串
DWORD nLength, // 字符个数
COORD dwWriteCoord, // 起始位置
LPDWORD lpNumberOfCharsWritten // 已写个数
);
可以看出:WriteConsoleOutput 函数功能相当于 SetConsoleTextAttribute 和
WriteConsole 的功能。而 WriteConsoleOutputCharacter 函数相当于
SetConsoleCursorPosition(设置光标位置)和 WriteConsole 的功能。不过在具体使用要注
意它们的区别。
(六)文本操作示例操作
下面看一个示例程序:
// 在具有阴影效果的窗口中显示一行字符
#include
HANDLE hOut;
void ShadowWindowLine(char *str);
void DrawBox(bool bSingle, SMALL_RECT rc); // 绘制边框
int main(void)
{
hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
SetConsoleOutputCP(437); // 设置代码页,这里如果设置成 936(简体中文),
那么程序会怎样?那样的话,将画不出边框。
ShadowWindowLine("Display a line of words, and center the window with
shadow.");
CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}
void ShadowWindowLine(char *str)
{
SMALL_RECT rc;
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口缓冲区信息
WORD att0,att1,attText;
int i, chNum = strlen(str);
GetConsoleScreenBufferInfo( hOut, &bInfo ); // 获取窗口缓冲区信息
// 计算显示窗口大小和位置
rc.Left = (bInfo.dwSize.X - chNum)/2 - 2;
rc.Top = 8; // 原代码段中此处为 bInfo.dwSize.Y/2 - 2,但是如果您的 DOS 屏
幕有垂直滚动条的话,还需要把滚动条下拉才能看到,为了方便就把它改为 10
rc.Right = rc.Left + chNum + 4;
rc.Bottom = rc.Top + 4;
att0 = BACKGROUND_INTENSITY; // 阴影属性
att1 = FOREGROUND_RED |FOREGROUND_GREEN |FOREGROUND_BLUE |
FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE;// 文本属性
attText = FOREGROUND_RED |FOREGROUND_INTENSITY; // 文本属性
// 设置阴影然后填充
COORD posShadow = {rc.Left+1, rc.Top+1}, posText = {rc.Left, rc.Top};
for (i=0; i<5; i++)
{
FillConsoleOutputAttribute(hOut, att0, chNum + 4, posShadow, NULL);
posShadow.Y++;
}
for (i=0;i<5;i++)
{
FillConsoleOutputAttribute(hOut, att1,chNum + 4, posText, NULL);
posText.Y++;
}
// 写文本和边框
posText.X = rc.Left + 2;
posText.Y = rc.Top + 2;
WriteConsoleOutputCharacter(hOut, str, strlen(str), posText, NULL);
DrawBox(true, rc);
SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性
}
void DrawBox(bool bSingle, SMALL_RECT rc) // 函数功能:画边框
{
char chBox[6];
COORD pos;
if (bSingle)
{
chBox[0] = (char)0xda; // 左上角点
chBox[1] = (char)0xbf; // 右上角点
chBox[2] = (char)0xc0; // 左下角点
chBox[3] = (char)0xd9; // 右下角点
chBox[4] = (char)0xc4; // 水平
chBox[5] = (char)0xb3; // 坚直
}
else
{
chBox[0] = (char)0xc9; // 左上角点
chBox[1] = (char)0xbb; // 右上角点
chBox[2] = (char)0xc8; // 左下角点
chBox[3] = (char)0xbc; // 右下角点
chBox[4] = (char)0xcd; // 水平
chBox[5] = (char)0xba; // 坚直
}
// 画边框的上 下边界
for(pos.X = rc.Left+1;pos.X
pos.X++;
}
// 画右上角
if(pos.X == rc.Right-2)
{
pos.X++;
WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);
pos.X--;
}
pos.Y = rc.Bottom;
// 画下边界
WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);
// 画左下角
if(pos.X == rc.Left+1)
{
pos.X--;
WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);
pos.X++;
}
// 画右下角
if(pos.X==rc.Right-2)
{
pos.X++;
WriteConsoleOutputCharacter(hOut, &chBox[3], 1, pos, NULL);
pos.X--;
}
}
// 画边框的左右边界
for (pos.Y = rc.Top+1; pos.Y<=rc.Bottom-1; pos.Y++)
{
pos.X = rc.Left;
// 画左边界
WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
pos.X = rc.Right-1;
// 画右边界
WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
}
}
程序运行结果如下图所示:
需要说明的是:
①在上述例子中,如果调用 DrawBox 函数时,传递的第一个参数不是 true 而是 false,
那么画出来的边框将是双线的。运行结果如下:
②如果在上述程序无法编译通过,您可以这样修改,即程序中调用
WriteConsoleOutputCharacter 和 FillConsoleOutputAttribute 函数的时候,最后一个参
数不用 NULL,而是先定义一个变量:
DWORD written;
然后把 &written 作为最后一个参数。
③上述程序在不同的字符代码页面(code page)下显示的结果是不同的。例如,中文
Windows 操作系统的默认代码页是简体中文(936),在该代码页面下值超过 128 的单字符在
Windows NT/XP 是显示不出来的。下表列出了可以使用的代码页。
代码页(Code page)
1258
1257
1256
1255
1254
1253
1252
1251
1250
950
949
936
932
874
850
437
说明
越南文
波罗的海文
阿拉伯文
希伯来文
土耳其语
希腊文
拉丁文(ANSI)
斯拉夫文
中欧文
繁体中文
韩文
简体中文
日文
泰文
使用多种语言(MS-DOS 拉丁文)
MS-DOS 美语/英语
ScrollConsoleScreenBuffer 是实现文本区滚动和移动的 API 函数。它可以将指定的一
块文本区域移动到另一个区域,被移空的那块区域由指定字符填充。函数的原型如下:
(七)滚动和移动操作
BOOL ScrollConsoleScreenBuffer(
HANDLE hConsoleOutput, // 句柄
CONST SMALL_RECT* lpScrollRectangle, // 要滚动或移动的区域
CONST SMALL_RECT* lpClipRectangle, // 裁剪区域
COORD dwDestinationOrigin, // 新的位置