控制台窗口界面控制设计

C语言控制台与基本图形界面编程指南(修订版) Windows API提供了大量的功能,涵盖了文件操作、系统服务、图形界面等多个方面。对于图形界面,它提供了诸如窗口创建、消息处理、绘图操作等基础功能。ncurses库允许程序员构建复杂的文本用户界面,如菜单、窗口和表单。它广泛应用于Unix/Linux系统下的终端应用程序。 阅读详情
AI摘要

博客给出了一个C++的文本操作示例程序,涉及在Windows环境下的相关操作,可能与buffer、output、button等元素有关。

 摘要: 文本界面的控制台应用程序开发是深入学习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程序。一旦C++控制台应用程序在Windows 9x/NT/2000操作系统中运行后,就会弹出一个窗口。例如下列过程:

  单击Visual C++标准工具栏上的“New Text File”按钮,打开一个新的文档窗口。

  选择File | Save菜单或按快捷键Ctrl+S或单击标准工具栏的Save按钮,弹出“保存为”文件对话框。将文件名为“Hello.cpp” (注意扩展名.cpp不能省略)。

  在文档窗口中输入下列代码:

#include
void main()
{
cout<<"Hello, Console!"<
}

  单击小型编译工具栏中的“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
void main()
{
HANDLE hOut;
hout = GetStdHandle(STD_OUTPUT_HANDLE);
// 获取标准输出设备句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口信息
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();
COORD pos = {0, 0};
FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X * bInfo.dwSize.Y, pos, NULL);
// 向窗口中填充字符以获得清屏的效果
CloseHandle(hOut); // 关闭标准输出设备句柄
}

程序中,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
void main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
// 获取标准输出设备句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口缓冲区信息
GetConsoleScreenBufferInfo(hOut, bInfo );
// 获取窗口缓冲区信息
char strTitle[255];
GetConsoleTitle(strTitle, 255); // 获取窗口标题
printf("当前窗口标题是:%s/n", strTitle);
_getch();
SetConsoleTitle("控制台窗口操作"); // 获取窗口标题
_getch();
COORD size = {80, 25};
SetConsoleScreenBufferSize(hOut,size); // 重新设置缓冲区大小
_getch();
SMALL_RECT rc = {0,0, 80-1, 25-1}; // 重置窗口位置和大小
SetConsoleWindowInfo(hOut,true ,&rc);
CloseHandle(hOut); // 关闭标准输出设备句柄
}

  需要说明的是,控制台窗口的原点坐标是(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 <windows.h>
HANDLE hOut;
void ShadowWindowLine(char *str); // 在具有阴影效果的窗口中显示一行字符,窗口为居中显示
void DrawBox(bool bSingle, SMALL_RECT rc); // 绘制边框
void main()
{
 hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
 SetConsoleOutputCP(437); // 设置代码页
 ShadowWindowLine("Display a line of words, and center the window with shadow.");
 CloseHandle(hOut); // 关闭标准输出设备句柄
}

void ShadowWindowLine(char *str)
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口缓冲区信息
 GetConsoleScreenBufferInfo( hOut, &bInfo ); // 获取窗口缓冲区信息
 // 计算显示窗口大小和位置
 int x1, y1, x2, y2, chNum = strlen(str);
 x1 = (bInfo.dwSize.X - chNum)/2 - 2;
 y1 = bInfo.dwSize.Y/2 - 2;
 x2 = x1 + chNum + 4;
 y2 = y1 + 5;
 WORD att1 = BACKGROUND_INTENSITY; // 阴影属性
 WORD att0 = FOREGROUND_RED |FOREGROUND_GREEN |FOREGROUND_BLUE |  
       FOREGROUND_INTENSITY |
       BACKGROUND_RED | BACKGROUND_BLUE; // 文本属性

 WORD attText = FOREGROUND_RED |FOREGROUND_INTENSITY; // 文本属性
 // 设置阴影
 COORD posShadow = {x1+1, y1+1}, posText = {x1, y1};
 for (int i=0; i<5; i++){
  FillConsoleOutputAttribute(hOut, att1, chNum + 4, posShadow, NULL);
  posShadow.Y++;
 }
 // 填充窗口背景
 for (i=0; i<5; i++){
  FillConsoleOutputAttribute(hOut, att0, chNum + 4, posText, NULL);
  posText.Y++;
 }
 // 写文本和边框
 posText.X = x1 + 2;
 posText.Y = y1 + 2;
 WriteConsoleOutputCharacter(hOut, str, strlen(str), posText, NULL);
 SMALL_RECT rc = {x1, y1, x2-1, y2-1};
 DrawBox(true, rc);
 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性
}

void DrawBox(bool bSingle, SMALL_RECT rc)
{
 char chBox[6];
 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; // 坚直
 }
 COORD pos = {rc.Left, rc.Top};
 WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL);

 for (pos.X = rc.Left + 1; pos.X
  WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

 pos.X = rc.Right;
 WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);

 for (pos.Y = rc.Top+1; pos.Y
 {
  pos.X = rc.Left;
  WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
  pos.X = rc.Right;
  WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
 }
 pos.X = rc.Left; pos.Y = rc.Bottom;
 WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);
 
 for (pos.X = rc.Left + 1; pos.X
  WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

 pos.X = rc.Right;
 WriteConsoleOutputCharacter(hOut, &chBox[3], 1, pos, NULL);
}
 
  程序运行结果如下图所示。
控制台窗口程序运行结果 
  需要说明的是,上述程序在不同的字符代码页面(code page)下显示的结果是不同的。例如,中文Windows操作系统的默认代码页是简体中文(936),在该代码页面下值超过128的单字符在 Windows NT/XP是显示不出来的。下表列出了可以使用的代码页。

代码页(Code page)说 明
1258越南文
1257波罗的海文
1256阿拉伯文
1255希伯来文
1254土耳其语
1253希腊文
1252拉丁文(ANSI)
1251斯拉夫文
1250中欧文
950繁体中文
949韩文
936简体中文
932日文
874泰文
850使用多种语言(MS-DOS拉丁文)
437MS-DOS美语/英语

  七、滚动和移动

  ScrollConsoleScreenBuffer是实现文本区滚动和移动的API函数。它可以将指定的一块文本区域移动到另一个区域,被移空的那块区域由指定字符填充。函数的原型如下:

BOOL ScrollConsoleScreenBuffer(
  HANDLE hConsoleOutput, // 句柄
  CONST SMALL_RECT* lpScrollRectangle, // 要滚动或移动的区域
  CONST SMALL_RECT* lpClipRectangle, // 裁剪区域
  COORD dwDestinationOrigin, // 新的位置
  CONST CHAR_INFO* lpFill // 填充字符
);

  利用这个API函数还可以实现删除指定行的操作。下面来举一个例子,程序如下:

#include
#include
#include
HANDLE hOut;
void DeleteLine(int row); // 删除一行
void MoveText(int x, int y, SMALL_RECT rc); // 移动文本块区域
void ClearScreen(void); // 清屏
void main()
{
 hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY |   
       BACKGROUND_BLUE ;
 // 背景是蓝色,文本颜色是黄色
 SetConsoleTextAttribute(hOut, att);
 ClearScreen();
 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");
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 COORD endPos = {0, bInfo.dwSize.Y - 1};
 SetConsoleCursorPosition(hOut, endPos); // 设置光标位置
 SMALL_RECT rc = {0, 2, 40, 5};
 _getch();
 MoveText(10, 5, rc);
 _getch();
 DeleteLine(5);
 CloseHandle(hOut); // 关闭标准输出设备句柄
}

void DeleteLine(int row)
{
 SMALL_RECT rcScroll, rcClip;
 COORD crDest = {0, row - 1};
 CHAR_INFO chFill;
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 rcScroll.Left = 0;
 rcScroll.Top = row;
 rcScroll.Right = bInfo.dwSize.X - 1;
 rcScroll.Bottom = bInfo.dwSize.Y - 1;
 rcClip = rcScroll;
 chFill.Attributes = bInfo.wAttributes;
 chFill.Char.AsciiChar = ' ';
 ScrollConsoleScreenBuffer(hOut, &rcScroll, &rcClip, crDest, &chFill);
}

void MoveText(int x, int y, SMALL_RECT rc)
{
 COORD crDest = {x, y};
 CHAR_INFO chFill;
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 chFill.Attributes = bInfo.wAttributes;
 chFill.Char.AsciiChar = ' ';
 ScrollConsoleScreenBuffer(hOut, &rc, NULL, crDest, &chFill);
}

void ClearScreen(void)
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 COORD home = {0, 0};
 WORD att = bInfo.wAttributes;
 unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;
 FillConsoleOutputAttribute(hOut, att, size, home, NULL);
 FillConsoleOutputCharacter(hOut, ' ', size, home, NULL);
}

  程序中,实现删除行的操作DeleteLine的基本原理是:首先将裁剪区域和移动区域都设置成指定行row(包括该行)以下的控制台窗口区域,然后将移动的位置指定为(0, row-1)。这样,超出裁剪区域的内容被裁剪掉,从而达到删除行的目的。

  需要说明的是,若裁剪区域参数为NULL,则裁剪区域为整个控制台窗口。
 
   八、光标操作

  控制台窗口中的光标反映了文本插入的当前位置,通过SetConsoleCursorPosition函数可以改变这个“当前”位置,这样就能控制字符(串)输出。事实上,光标本身的大小和显示或隐藏也可以通过相应的API函数进行设定。例如:

BOOL SetConsoleCursorInfo( // 设置光标信息
 HANDLE hConsoleOutput, // 句柄
 CONST CONSOLE_CURSOR_INFO *lpConsoleCursorInfo // 光标信息
);

BOOL GetConsoleCursorInfo( // 获取光标信息
 HANDLE hConsoleOutput, // 句柄
 PCONSOLE_CURSOR_INFO lpConsoleCursorInfo // 返回光标信息
);

  这两个函数都与CONSOLE_CURSOR_INFO结构体类型有关,其定义如下:

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize; // 光标百分比大小
 BOOL bVisible; // 是否可见
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

  需要说明的是,dwSize值反映了光标的大小,它的值范围为1-100;当为1时,光标最小,仅是一条最靠下的水平细线,当为100,光标最大,为一个字符大小的方块。

  九、读取键盘信息

  键盘事件通常有字符事件和按键事件,这些事件所附带的信息构成了键盘信息。它是通过API函数ReadConsoleInput来获取的,其原型如下:

BOOL ReadConsoleInput(
 HANDLE hConsoleInput, // 输入设备句柄
 PINPUT_RECORD lpBuffer, // 返回数据记录
 DWORD nLength, // 要读取的记录数
 LPDWORD lpNumberOfEventsRead // 返回已读取的记录数
);

  其中,INPUT_RECORD定义如下:

typedef struct _INPUT_RECORD {
 WORD EventType; // 事件类型
 union {
  KEY_EVENT_RECORD KeyEvent;
  MOUSE_EVENT_RECORD MouseEvent;
  WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
  MENU_EVENT_RECORD MenuEvent;
  FOCUS_EVENT_RECORD FocusEvent;
 } Event;
} INPUT_RECORD;

  与键盘事件相关的记录结构KEY_EVENT_RECORD定义如下:

typedef struct _KEY_EVENT_RECORD {
 BOOL bKeyDown; // TRUE表示键按下,FALSE表示键释放
 WORD wRepeatCount; // 按键次数
 WORD wVirtualKeyCode; // 虚拟键代码
 WORD wVirtualScanCode; // 虚拟键扫描码
 union {
  WCHAR UnicodeChar; // 宽字符
  CHAR AsciiChar; // ASCII字符
 } uChar; // 字符
 DWORD dwControlKeyState; // 控制键状态
} KEY_EVENT_RECORD;

  我们知道,键盘上每一个有意义的键都对应着一个唯一的扫描码,虽然扫描码可以作为键的标识,但它依赖于具体设备的。因此,在应用程序中,使用的往往是与具体设备无关的虚拟键代码。这种虚拟键代码是与设备无关的键盘编码。在Visual C++中,最常用的虚拟键代码已被定义在Winuser.h中,例如:VK_SHIFT表示SHIFT键,VK_F1表示功能键F1等。

  上述结构定义中,dwControlKeyState用来表示控制键状态,它可以是CAPSLOCK_ON(CAPS LOCK灯亮)、ENHANCED_KEY(按下扩展键)、LEFT_ALT_PRESSED(按下左ALT键)、LEFT_CTRL_PRESSED (按下左CTRL键)、NUMLOCK_ON (NUM LOCK灯亮)、RIGHT_ALT_PRESSED(按下右ALT键)、RIGHT_CTRL_PRESSED(按下右CTRL键)、 SCROLLLOCK_ON(SCROLL LOCK灯亮)和SHIFT_PRESSED(按下SHIFT键)中的一个或多个值的组合。

  下面的程序是将用户按键的字符输入到一个控制台窗口的某个区域中,并当按下NUM LOCK、CAPS LOCK和SCROLL LOCK键时,在控制台窗口的最后一行显示这些键的状态。

#include
HANDLE hOut;
HANDLE hIn;
void DrawBox(bool bSingle, SMALL_RECT rc);
void ClearScreen(void);
void CharWindow(char ch, SMALL_RECT rc); // 将ch输入到指定的窗口中
void ControlStatus(DWORD state); // 在最后一行显示控制键的状态
void DeleteTopLine(SMALL_RECT rc); // 删除指定窗口中最上面的行并滚动
void main()
{
 hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
 hIn = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄
 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY |  
       BACKGROUND_BLUE ;
 // 背景是蓝色,文本颜色是黄色
 SetConsoleTextAttribute(hOut, att);
 ClearScreen(); // 清屏
 INPUT_RECORD keyRec;
 DWORD state = 0, res;
 char ch;
 SMALL_RECT rc = {20, 2, 40, 12};
 DrawBox(true, rc);
 COORD pos = {rc.Left+1, rc.Top+1};
 SetConsoleCursorPosition(hOut, pos); // 设置光标位置
 for(;;) // 循环
 {
  ReadConsoleInput(hIn, &keyRec, 1, &res);
  if (state != keyRec.Event.KeyEvent.dwControlKeyState) {
   state = keyRec.Event.KeyEvent.dwControlKeyState;
   ControlStatus(state);
  }
  if (keyRec.EventType == KEY_EVENT){
   if (keyRec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) break;
   // 按ESC键退出循环
   if (keyRec.Event.KeyEvent.bKeyDown) {
    ch = keyRec.Event.KeyEvent.uChar.AsciiChar;
    CharWindow(ch, rc);
   }
  }
 }
 pos.X = 0; pos.Y = 0;
 SetConsoleCursorPosition(hOut, pos); // 设置光标位置
 CloseHandle(hOut); // 关闭标准输出设备句柄
 CloseHandle(hIn); // 关闭标准输入设备句柄
}

void CharWindow(char ch, SMALL_RECT rc) // 将ch输入到指定的窗口中
{
 static COORD chPos = {rc.Left+1, rc.Top+1};
 SetConsoleCursorPosition(hOut, chPos); // 设置光标位置
 if ((ch<0x20)||(ch>0x7e)) return;
 WriteConsoleOutputCharacter(hOut, &ch, 1, chPos, NULL);
 if (chPos.X>=(rc.Right-1))
 {
  chPos.X = rc.Left;
  chPos.Y++;
 }
 if (chPos.Y>(rc.Bottom-1))
 {
  DeleteTopLine(rc);
  chPos.Y = rc.Bottom-1;
 }
 chPos.X++;
 SetConsoleCursorPosition(hOut, chPos); // 设置光标位置
}

void ControlStatus(DWORD state) // 在最后一行显示控制键的状态
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 COORD home = {0, bInfo.dwSize.Y-1};
 WORD att0 = BACKGROUND_INTENSITY ;
 WORD att1 = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED;
 FillConsoleOutputAttribute(hOut, att0, bInfo.dwSize.X, home, NULL);
 FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X, home, NULL);
 SetConsoleTextAttribute(hOut, att1);
 COORD staPos = {bInfo.dwSize.X-16,bInfo.dwSize.Y-1};
 SetConsoleCursorPosition(hOut, staPos);
 if (state & NUMLOCK_ON)
 WriteConsole(hOut, "NUM", 3, NULL, NULL);
 staPos.X += 4;
 SetConsoleCursorPosition(hOut, staPos);
 if (state & CAPSLOCK_ON)
 WriteConsole(hOut, "CAPS", 4, NULL, NULL);
 staPos.X += 5;
 SetConsoleCursorPosition(hOut, staPos);
 if (state & SCROLLLOCK_ON)
 WriteConsole(hOut, "SCROLL", 6, NULL, NULL);
 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性
 SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢复原来的光标位置
}

void DeleteTopLine(SMALL_RECT rc)
{
 COORD crDest;
 CHAR_INFO chFill;
 SMALL_RECT rcClip = rc;
 rcClip.Left++; rcClip.Right--;
 rcClip.Top++; rcClip.Bottom--;
 crDest.X = rcClip.Left;
 crDest.Y = rcClip.Top - 1;
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 chFill.Attributes = bInfo.wAttributes;
 chFill.Char.AsciiChar = ' ';
 ScrollConsoleScreenBuffer(hOut, &rcClip, &rcClip, crDest, &chFill);
}

  程序运行结果如下图所示:
  

 

十、读取鼠标信息

  与读取键盘信息方法相似,鼠标信息也是通过ReadConsoleInput来获取的,其MOUSE_EVENT_RECORD具有下列定义:

typedef struct _MOUSE_EVENT_RECORD {
 COORD dwMousePosition; // 当前鼠标位置
 DWORD dwButtonState; // 鼠标按钮状态
 DWORD dwControlKeyState; // 键盘控制键状态
 DWORD dwEventFlags; // 事件状态
} MOUSE_EVENT_RECORD;

  其中,dwButtonState反映了用户按下鼠标按钮的情况,它可以是:FROM_LEFT_1ST_BUTTON_PRESSED(最左边按钮)、RIGHTMOST_BUTTON_PRESSED(最右边按钮)、FROM_LEFT_2ND_BUTTON_PRESSED(左起第二个按钮)、FROM_LEFT_3RD_BUTTON_PRESSED(左起第三个按钮)和FROM_LEFT_4TH_BUTTON_PRESSED (左起第四个按钮)。而dwEventFlags表示鼠标的事件,如DOUBLE_CLICK(双击)、MOUSE_MOVED(移动)和 MOUSE_WHEELED(滚轮滚动,只适用于Windows 2000/XP)。dwControlKeyState的含义同前。

  下面举一个例子。这个例子能把鼠标的当前位置显示在控制台窗口的最后一行上,若单击鼠标左键,则在当前位置处写一个字符‘A’,若双击鼠标任一按钮,则程序终止。具体代码如下:

#include <WINDOWS.H>
#include <STDIO.H>
#include <STRING.H>
HANDLE hOut;
HANDLE hIn;
void ClearScreen(void);
void DispMousePos(COORD pos); // 在最后一行显示鼠标位置
void main()
{
 hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
 hIn = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄
 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY |  
       BACKGROUND_BLUE ;
 // 背景是蓝色,文本颜色是黄色
 SetConsoleTextAttribute(hOut, att);
 ClearScreen(); // 清屏
 INPUT_RECORD mouseRec;
 DWORD state = 0, res;
 COORD pos = {0, 0};
 for(;;) // 循环
 {
  ReadConsoleInput(hIn, &mouseRec, 1, &res);
  if (mouseRec.EventType == MOUSE_EVENT){
   if (mouseRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK) break;
    // 双击鼠标退出循环
    pos = mouseRec.Event.MouseEvent.dwMousePosition;
    DispMousePos(pos);
    if (mouseRec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
          FillConsoleOutputCharacter(hOut, 'A', 1, pos, NULL);
   }
  }
  pos.X = 0; pos.Y = 0;
  SetConsoleCursorPosition(hOut, pos); // 设置光标位置
  CloseHandle(hOut); // 关闭标准输出设备句柄
  CloseHandle(hIn); // 关闭标准输入设备句柄
 }

void DispMousePos(COORD pos) // 在最后一行显示鼠标位置
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 COORD home = {0, bInfo.dwSize.Y-1};
 WORD att0 = BACKGROUND_INTENSITY ;
 FillConsoleOutputAttribute(hOut, att0, bInfo.dwSize.X, home, NULL);
 FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X, home, NULL);
 char s[20];
 sprintf(s,"X = %2lu, Y = %2lu",pos.X, pos.Y);
 SetConsoleTextAttribute(hOut, att0);
 SetConsoleCursorPosition(hOut, home);
 WriteConsole(hOut, s, strlen(s), NULL, NULL);
 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性
 SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢复原来的光标位置
}

void ClearScreen(void)
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo );
 COORD home = {0, 0};
 unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;
 FillConsoleOutputAttribute(hOut, bInfo.wAttributes, size, home, NULL);
 FillConsoleOutputCharacter(hOut, ' ', size, home, NULL);
}

  程序运行结果如下:
 

  十一、结语

  综上所述,利用控制台窗口的Widows API函数可以设计简洁美观的文本界面,使得用Visual C++ 6.0开发环境深入学习C++以及文本界面设计成为一件比较容易的事件。当然文本界面的设计还需要一定的方法和技巧,限于篇幅,这里不再阐述。
使用Visual Basic创建控制台窗口模拟应用 Visual Basic (VB)作为一种简单易学的编程语言,其强大的集成开发环境(IDE)允许开发者通过简单的代码来模拟一个功能完备的控制台窗口。该功能对于需要进行底层操作或运行命令行指令的应用程序来说至关重要。在这第一章中,我们将简要介绍模拟控制台窗口的基本概念,以及在VB中实现控制台窗口的初步思路。控制台窗口通常用于显示程序的输出和接收用户的输入,是命令行程序运行的宿主环境。通过模拟控制台窗口,开发者可以在图形用户界面(GUI)中嵌入命令行操作的体验,提升软件的交互性和功能丰富性。 阅读详情

相关推荐

Cursor概述及环境配置

Cursor 是一款基于 AI 的代码编辑器,支持 VS Code 配置迁移和 Java 开发环境配置。主要功能分为 Cursor Settings(AI 相关设置)和 Editor Settings(基础编辑器设置)。用户可通过一键导入或手动方式迁移 VS Code 配置,配置包括主题、扩展和快捷键等。对于 Java 开发,需配置 JDK 和 Maven 路径,并安装 Java 扩展插件以获得完整开发体验,如代码补全、调试和 Maven 项目管理。此外,Cursor 支持汉化配置,用户可安装中文语言包切换

Knight 3548

在GUI程序中使用控制台的两种方法-方法.1

第2学堂-专栏 4749

Android自动化新方案:ESP32-C3蓝牙HID模拟触控与手势操作实战(附源码)

本文详细介绍了使用ESP32-C3通过蓝牙HID协议实现Android设备自动化操作的完整方案。该硬件方案成本低廉且能完美规避系统风控,可模拟鼠标移动、点击及复杂手势操作,延迟低于50毫秒,适用于游戏连招和应用测试等场景。文章包含完整的源码实现和性能优化技巧。

kotlin6android的博客 377

Win32 Application和Win32 Console Application

1. Win32 Application和Win32 Console Application的区别     win32应用程序是有窗体的(当然也可以没有),有Windows消息循环机制的。而win32控制台应用程序只是在控制台下运行的程序,类似以前dos的程序。     Win32 Application和Win32 Console Application 都是工作在32位Windows环境的程序。 其中Win32 Application 就是普通的常见的窗口应用程序,当然有的界面做得比较个性化,比如圆形的

ym12345678901的博客 1837

OpenRA引擎实战:如何从零开始搭建一个经典RTS游戏模组(附资源包)

本文详细介绍了如何利用开源游戏引擎OpenRA从零开始搭建经典RTS游戏模组。内容涵盖开发环境配置、资源管线处理、YAML规则定义、地图编辑与Lua脚本编写等全流程实战指南,并提供了资源包,帮助开发者和爱好者快速创建属于自己的即时战略游戏世界。

silver的专栏 902

Ubuntu22.04+Docker一键部署Autoware Universe避坑指南(附鱼香ROS配置)

本文详细介绍了在Ubuntu22.04系统中使用Docker一键部署Autoware Universe的完整流程,包括鱼香ROS工具链配置、Docker环境优化及VSCode开发环境集成。通过实战指南帮助开发者快速搭建自动驾驶开发环境,解决常见部署问题,提升开发效率。

weixin_29254673的博客 413

让VS2008下控制台程序窗口一直在

有时候用VS2008编写控制台程序看看算法写对了没有,在编译通过后,控制台窗口一闪而过,在百度上搜了搜,可以用以下方法让控制台窗口一直存在: 包含头文件: #include #include   在程序返回之前加上:system("pause");

jh616359023的专栏 871

SpringBoot整合海康SDK实战:从设备初始化到PS流录像转MP4全流程(附避坑指南)

本文详细介绍了SpringBoot整合海康SDK的完整实战流程,涵盖设备初始化、通道管理、录像检索与下载。针对海康设备生成的PS流录像文件无法直接播放的痛点,重点解析了如何利用FFmpeg高效、无损地将其转换为通用的MP4格式,并提供了关键的避坑指南和性能优化建议。

chair的专栏 763

控制台程序和窗口程序

控制台程序是为了兼容dos而设立的,这种程序的运行就好像在一个窗口中运行一样,没有自己的界面(就是c++和vc++中的黑框)dos中只能是一个任务执行完之后再执行另一个任务。单任务。 此问题是由于使用VS编写程序时,出现了链接错误(就是因为单任务的控制台程序没有关闭,导致链接失败。新手学习,具体深层的还没有理解) 窗口程序多是指windows系统中的运行程序是已窗口打开的,如对话框等,可关闭或

szl_lxy 1945

【DSP】【工程实战】从零构建C6678多核工程:CCS环境配置与启动模式详解

本文详细介绍了在CCS开发环境中从零构建TMS320C6678多核DSP工程的完整流程。内容涵盖KeyStone架构解析、工程创建、基础代码编写、链接命令文件配置、仿真器调试,并重点深入讲解了包括I2C EEPROM在内的多种一级启动模式原理与配置方法,为开发者提供了一套清晰、可复用的实战指南。

weixin_29164185的博客 416

即插即用系列(代码实践) | WACV 2024 CSAM:面向各向异性医学图像分割的 2.5D 跨切片注意力模块

文章摘要: 论文《CSAM: A 2.5D Cross-Slice Attention Module for Anisotropic Volumetric Medical Image Segmentation》提出了一种解决医学图像各向异性问题的创新模块CSAM。针对MRI/CT数据中切片间分辨率差异大的挑战,该模块通过2.5D跨切片注意力机制,结合2D卷积的轻量性和3D上下文感知能力。核心包括语义注意力(通道级)、位置注意力(空间级)和跨切片注意力(支持不确定性建模),能在几乎不增加参数量的情况下有效捕捉

分享顶会顶刊论文即插即用模块 104

VC如何在控制台应用程序下创建Windows窗口界面

在VC中,我们可以创建两类应用程序,一种是控制台下的,只有一个黑窗口,dos风格的,只能用键盘输入来交互的;另一种是Windows的窗口界面的,用鼠标点来点去操作交互的。前者通过main函数开始,即程序的入口点,后者呢,通过WinMain开始程序的执行。一个疑惑就是这两种方式究竟造成的区别咋就那么大呢?是不是写Windows视窗程序,必须定义WinMain函数呢? 答案:不一定!比如OpenCV...

格物致知的专栏 [音视频编解码 网络协议 计算机视觉 计算机图形学 图像理解 语音识别 机器学习 模拟电路 传感器] 2707

【数据同化第一期】基本概念介绍

【数据同化第一期】基本概念介绍

WW、forever的博客 1901

控制台窗口界面控制设计(一)

摘要: 文本界面的控制台应用程序开发是深入学习C++、掌握交互系统的实现方法的最简单的一种手段。然而,Visual C++的C++专用库却没有TC所支持的文本(字符)屏幕控制函数,为此本系列文章从一般控制步骤、控制台窗口操作、文本(字符)控制、滚动和移动、光标、键盘和鼠标等几个方面讨论控制台窗口界面的编程控制方法。  在众多C++开发工具中,由于Microsoft本身的独特优势,选用Visual

流水一样的年华 1255

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

控制台窗口界面控制设计(090807)

  摘要: 文本界面的控制台应用程序开发是深入学习C++、掌握交互系统的实现方法的最简单的一种手段。然而,Visual C++的C++专用库却没有TC所支持的文本(字符)屏幕控制函数,为此本系列文章从一般控制步骤、控制台窗口操作、文本(字符)控制、滚动和移动、光标、键盘和鼠标等几个方面讨论控制台窗口界面的编程控制方法。  在众多C++开发工具中,由于Microsoft本身的独特优势,选用Vi...

weixin_30546933的博客 135

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

控制台刷新界面c语言程序设计教程课后答案,【C语言】控制台窗口图形界面编程(二)窗口信息和填充缓冲区...

00. 目录[TOC]01. COORD结构体定义控制台屏幕缓冲区中字符单元格的坐标。坐标系(0,0)的原点位于缓冲区的左上角。类型声明typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;说明:x: 水平坐标或列值。单位取决于函数调用。y: 垂直坐标或行值。单位取决于函数调用。02. SMALL_RECT结构定义矩形的左上角和右下角的坐标...

weixin_39665060的博客 553

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

c语言让控制台窗口停留_C语言控制台窗口界面编程(修正版)

〇、摘要一、概述二、控制台文本窗口的一般控制步骤三、控制台窗口操作四、文本属性操作五、文本输出六、文本操作示例七、滚动和移动八、光标操作九、读取键盘信息十、读取鼠标信息十一、结语补充篇--经典程序(Internet资源)摘要:文本界面的控制台应用程序开发是深入学习C++、掌握交互系统的实现方法的最简单的一种手段。然而,VisualC++的C++专用库却没有TC所支持的文本(字符)屏幕控制函数,为...

weixin_39946364的博客 848

C语言控制台窗口图形界面编程(总结)

<知识分享(来自我的优快云)> :http://blog.youkuaiyun.com/jzqt_t/article/details/32225195 本系列文章是笔者通过学习《C语言控制台窗口界面编程(修正版)》而写(关于此文档的更多信息请看本系列文章第一篇),旨在让大家更加清晰简洁容易地学习C语言控制台窗口界面的编程。 通过本系列文章我们可以在控制台窗口中制作好看...

weixin_30624825的博客 247

【转】控制台窗口界面的编程控制(一)

文本界面的控制台应用程序开发是深入学习C++、掌握交互系统的实现方法的最简单的一种手段。然而,Visual C++的C++专用库却没有TC所支持的文本(字符)屏幕控制函数,为此本系列文章从一般控制步骤、控制台窗口操作、文本(字符)控制、滚动和移动、光标、键盘和鼠标等几个方面讨论控制台窗口界面的编程控制方法。  在众多C++开发工具中,由于Microsoft本身的独特优势,选用Visual...

weixin_30909575的博客 129

C++控制台操作(基本操作的代码)

控制台窗口界面编程控制 〇、摘要 一、概述 二、控制台文本窗口的一般控制步骤 三、控制台窗口操作 四、文本属性操作 五、文本输出 六、文本操作示例 七、滚动和移动 八、光标操作 九、读取键盘信息 十、读取鼠标信息 十一、结语 补充篇--经典程序(Internet资源) 摘要: 文本界面的控制台应用程序开发是深入学习C++、掌握交互系统的实现方法的最简单的一种手段。然而...

骐骥一跃,不能十步;驽马十驾,功在不舍。 4876

c语言控制台窗口图形界面编程(四) - c语言程序开发技术文章,C语言控制台窗口图形界面编程(总结)...

:http://blog.youkuaiyun.com/jzqt_t/article/details/32225195本系列文章是笔者通过学习《C语言控制台窗口界面编程(修正版)》而写(关于此文档的更多信息请看本系列文章第一篇),旨在让大家更加清晰简洁容易地学习C语言控制台窗口界面的编程。通过本系列文章我们可以在控制台窗口中制作好看的图形界面,方便了在非TC开发环境中的控制台窗口中进行文本界面和图形界面...

weixin_29602351的博客 432

C语言实现贪吃蛇之图形界面篇

这已经是贪吃蛇系列的第五篇了,讲真一直写这个也挺无聊的,所以这一篇博文将是系列的最后一篇。虽然已经介绍了贪吃蛇的几种写法,但说到底我们的游戏还只是在一个黑框框里移动的星号。和我们平时玩的贪吃蛇游戏有不少差距。游戏嘛,画面也是很重要的一环。接下来就是让之前的贪吃蛇游戏脱胎换骨的时候了。话不多说,这就开干吧。   首先,为了摆脱无趣的黑框(控制台),我们这次新建一个win32项目,直接点击完成。

liutong123987的专栏 1万+
上一篇: 用控件(CMSComm)读串口代码(高效率,不同于网上常见的,决没有抄袭)!
下一篇: 用VC编写具有动态换肤功能的应用程序
xmlscript
博客等级 码龄25年 1粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值