win32-API

win32-API

C++控制台编程

我们的控制台文本窗口是基于win32 api实现的
我们以例子来学习这种编程的方法:
控制台程序的外观和操作就像MS-DOS窗口一样,控制台有一个输入缓冲区以及一个或多个屏幕缓冲区:

  • 输入缓存区(input buffer)包含一组输入记录(input records),其中每个记录都是一个输入事件的数据。输入事件的例子包括键盘输入,鼠标点击,以及用户调整控制台窗口大小
  • 屏幕缓冲区(screen buffer)是字符与颜色数据的二维数组,他会影响控制台窗口文本的外观

例1:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <windows.h>
int main()
{
printf("Hello World!\n");
Sleep(1000);
system("cls");
return 0;
}

我们发现这个程序和我们的一般的hello world程序不同,它实现的功能是在1000ms后清空控制台的内容,使用了sleep和system命令。

例2:

1
2
3
4
5
6
7
8
#include <windows.h>
#include <stdio.h>

int main(void){
SetConsoleTitle(L"hello world!"); // 设置窗口标题
printf("hello world!");
return 0;
}

我们的使用了windows.h里的函数,我们想要编写控制台应用肯定需要用到这个库
SetConsoleTitle 用于设置窗口标题,这里L代表宽字符,用L标识的是宽字符,标准的字符是一个字符一个字节,宽的是一个字符两个字节。
用于控制台窗口操作的API函数如下:

  • GetConsoleScreenBufferInfo 获取控制台窗口信息
  • GetConsoleTitle 获取控制台窗口标题
  • ScrollConsoleScreenBuffer 在缓冲区中移动数据块
  • SetConsoleScreenBufferSize 更改指定缓冲区大小
  • SetConsoleTitle 设置控制台窗口标题
  • SetConsoleWindowInfo 设置控制台窗口信息

例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <windows.h>
using namespace std;

int main()
{

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo; //声明保存控制台信息结构体指针
GetConsoleScreenBufferInfo(hOutput, &bInfo); // 获取信息

SMALL_RECT rc = {0, 0, 10, 10}; // 坐标位置结构体初始化,分别表示左上右下
SetConsoleWindowInfo(hOutput, true ,&rc); //设置区域坐标
cout << "窗口显示坐标位置:" << bInfo.srWindow.Left << ", " <<

COORD dSiz = {80, 80}; //设置coord,表示一个字符在控制台屏幕上的xy坐标
SetConsoleScreenBufferSize(hOutput, dSiz);

CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}

GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *bInfo)
第一个参数hConsoleOutput参数(标准控制句柄)通过GetStdHandle()函数返回值获得
第二个参数CONSOLE_SCREEN_BUFFER_INFO 保存控制台信息结构体指针
属性如下:
COORD dwSize; // 缓冲区大小
COORD dwCursorPosition; //当前光标位置
WORD wAttributes; //字符属性
SMALL_RECT srWindow; //当前窗口显示的大小和位置
COORD dwMaximumWindowSize; //最大的窗口缓冲区大小

若bInfo是一个CONSOLE_SCREEN_BUFFER_INFO类型的结构体,那么访问属性的方法如下:

  • bInfo.srWindow.Top 获取窗口相对于Top位置
  • bInfo.srWindow.Right 获取窗口相对于Right的位置
  • bInfo.dSiz.X 获取缓存区的X位置

其余一些原型如下:
SetConsoleWindowInfo(HANDLE, BOOL, SMALL_RECT *);
SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize)

GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize)
lpConsoleTitle为指向一个缓冲区指针以接收包含标题的字符串;nSize由lpConsoleTitle指向的缓冲区大小

例4

1
2
3
4
5
6
7
8
9
10
11
#include "stdafx.h"
#include<windows.h>

int main() {
HWND window; //定义一个窗口句柄变量,用来储存窗口句柄
/*FindWindow("这里填窗口类名","这里填窗口标题名")
窗口类名和窗口标题名可以只填一个,不填的用NULL填充*/
window = FindWindow(NULL,"文本.txt"); //查找标题为"文本.txt"的窗口
SendMessage(window,WM_CLOSE,0,0); //向窗口发送关闭指令
return 0;
}

句柄不用多说,在Windows中,句柄是一个系统内部数据结构的引用。例如当你操作一个窗口时系统会给你一个该窗口的句柄,系统会通知你:你正在操作142号窗口,就此你的应用程序就能要求系统对142号窗口进行操作——移动窗口、改变窗口大小、把窗口最小化等等。

例5

操作文本输出函数有:

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
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); // 已写个数

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

看例子:

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
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
WORD wr1 = 0xfa;//定义颜色属性;第一位为背景色,第二位为前景色
SetConsoleTextAttribute(hOut, wr1);
cout << "hello world!" << endl;
WORD wr2 = FOREGROUND_RED | FOREGROUND_INTENSITY;//方法二用系统宏定义颜色属性
SetConsoleTextAttribute(hOut, wr2);
cout << "hello world!" << endl;


//输出文本
SetConsoleTextAttribute(hOut, 0x0f);
cout << "00000000000000000000000000000" << endl;
cout << "00000000000000000000000000000" << endl;
cout << "00000000000000000000000000000" << endl;
cout << "00000000000000000000000000000" << endl;

SMALL_RECT CutScr = {1, 2, 10, 4}; //裁剪区域与目标区域的集合行成剪切区域
SMALL_RECT PasScr = {7, 2, 11, 9}; //可以是NULL,即全区域
COORD pos = {1, 8}; //起点坐标,与裁剪区域长宽构成的区域再与目标区域的集合为粘贴区

//定义填充字符的各个参数及属性
SetConsoleTextAttribute(hOut, 0x1);
CONSOLE_SCREEN_BUFFER_INFO Intsrc;
GetConsoleScreenBufferInfo(hOut, &Intsrc);
CHAR_INFO chFill = {'A', Intsrc.wAttributes}; //定义剪切区域填充字符
ScrollConsoleScreenBuffer(hOut, &CutScr, &PasScr, pos, &chFill); //移动文本

CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}

由于剪切区域是两个区域的集合,我们第一行到第三行的第7个0到第11个0会被剪掉(前面有2行helloworld),注意,这里的rect的位置为其前面的字符个数或行数

例6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
cout << "hello world!" << endl;
Sleep(2000);//延时函数
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
COORD w = {0, 0};
SetConsoleCursorPosition(hOut, w);
CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE};
Sleep(2000);
SetConsoleCursorInfo(hOut, &cursorInfo);
CursorInfo.bVisible = false; //隐藏控制台光标
Sleep(2000);
CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}

输出hello world!后延时两秒,光标从第二行移到行首,再2秒后光标隐藏不显示,再过2秒程序结束

1
2
3
4
5
6
7
8
//设置光标位置
SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD dwCursorPosition);
//设置光标信息
BOOL SetConsoleCursorInfo(HANDLE hConsoleOutput, PCONST PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
//获取光标信息
BOOL GetConsoleCursorInfo(HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
//参数1:句柄;参数2:CONSOLE_CURSOR_INFO结构体:
//DWORD dwSize;(光标大小取值1-100)BOOL bVisible;(是否可见)

例7

GetCursorPos和SetCursorPos一组可以获取和设置鼠标的位置
SetCursorPos给一组x,y就可以设置
需要注意的是GetCursorPos:
POINT mouse; //用来储存鼠标的x y坐标
GetCursorPos(&mouse); //调用GetCursorPos函数获取坐标值
printf("%d,%d\n",mouse.x,mouse.y);

当要设置控制台光标的时候,使用SetConsoleCursorPosition

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
#include <iostream>
#include <windows.h>
#include <conio.h>
using namespace std;
HANDLE hOut;
//清除函数
void cle(COORD ClPos)
{
SetConsoleCursorPosition(hOut, ClPos);
cout << " " << endl;
}
//打印函数
void prin(COORD PrPos)
{
SetConsoleCursorPosition(hOut, PrPos);
cout << "@" << endl;
}
//移动函数
void Move(COORD *MoPos, int key)
{
switch(key)
{
case 72: MoPos->Y--;break;
case 75: MoPos->X--;break;
case 77: MoPos->X++;break;
case 80: MoPos->Y++;break;
default: break;
}
}

int main()
{
cout << "用方向键移动下行输出内容" << endl;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);//取句柄
COORD CrPos = {0, 1};//保存光标信息
prin(CrPos);//打印
//等待键按下
while(1)
{
if(kbhit())
{
cle(CrPos);//清除原有输出
Move(&CrPos, getch());
prin(CrPos);
}
}
return 0;
}

例8

读取键盘信息操作

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

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

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <windows.h>
#include <stdio.h>

int main(void)
{
// 获取标准输入输出设备句柄
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);

DWORD dwRes, dwState=0;
INPUT_RECORD keyRec;
COORD crHome={0, 0}, crPos;
bool bCaps, bNum, bScroll;
char ch;
CONSOLE_SCREEN_BUFFER_INFO bInfo;

// 输出三个灯的初始状态
printf("状态:CAPS LOCK:CLOSE NUM LOCK:CLOSE SCROLL LOCK:CLOSE\n");

while (1) // 消息循环
{
ReadConsoleInput(hIn, &keyRec, 1, &dwRes);

if (keyRec.EventType == KEY_EVENT)
{
// 只在按下时判断,弹起时不判断
if (keyRec.Event.KeyEvent.bKeyDown)
{
// 当三个灯的状态发生改变
if (dwState != keyRec.Event.KeyEvent.dwControlKeyState)
{
dwState = keyRec.Event.KeyEvent.dwControlKeyState;
bCaps = bNum = bScroll = false;

// 判断三个灯是否有亮
if (dwState & CAPSLOCK_ON)
{
bCaps = true;
}
if (dwState & NUMLOCK_ON)
{
bNum = true;
}
if (dwState & SCROLLLOCK_ON)
{
bScroll = true;
}

// 得到控制台信息(包括输出前光标的位置)
GetConsoleScreenBufferInfo(hOut, &bInfo);
SetConsoleCursorPosition(hOut,crHome);

// 输出三个灯的状态
printf("状态:CAPS LOCK:%s NUM LOCK:%s SCROLL LOCK:%s\n",
bCaps?"OPEN ":"CLOSE",
bNum?"OPEN ":"CLOSE",
bScroll?"OPEN ":"CLOSE");

// 还原光标位置
SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition);
}

// -- 基础功能键
switch(keyRec.Event.KeyEvent.wVirtualKeyCode)
{
case VK_RETURN: // 按回车时跳到下一行
printf("\n");
break;

case VK_SPACE: // 按空格时输出一个空格
printf(" ");
break;

case VK_BACK: // 按删除时删掉一个字符(只能当前行操作)
GetConsoleScreenBufferInfo(hOut, &bInfo);
crPos = bInfo.dwCursorPosition;
if (crPos.X!=0)
{
crPos.X -= 1;
}
SetConsoleCursorPosition(hOut,crPos);
printf(" ");
SetConsoleCursorPosition(hOut,crPos);
break;

case VK_ESCAPE: // 按ESC键时退出
CloseHandle(hOut); // 关闭标准输出设备句柄
CloseHandle(hIn); // 关闭标准输入设备句柄
return 0;

default:
break;
}

// -- 打印字符
ch = keyRec.Event.KeyEvent.uChar.AsciiChar;

// 输出可以打印的字符(详参ASCII表)
if (ch>0x20 && ch<0x7e)
{
putchar(ch);
}
}
}
}

return 0;
}

EventType有5种,当要使用键盘事件时,应该先判断 EventType 是否为 KeyEvent,然后使用 KEY_EVENT_RECORD,判断现在的键盘是什么情况。其他事件也是一样的(一般只使用键盘和鼠标事件)

  • FOCUS_EVENT

  • KEY_EVENT // 键盘事件

  • MENU_EVENT

  • MOUSE_EVENT // 鼠标事件

  • WINDOW_BUFFER_SIZE_EVENT
    键盘的结构如下:
    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;

所以当我们想要打印字符的时候,就使用

1
2
3
4
5
ch = keyRec.Event.KeyEvent.uChar.AsciiChar;
// 输出可以打印的字符(详参ASCII表)
if (ch>0x20 && ch<0x7e){
putchar(ch);
}
Author

Ctwo

Posted on

2019-08-24

Updated on

2020-10-25

Licensed under

Comments