视频参考:https://www.bilibili.com/video/BV1C2miYPEQf/
WNDCLASSA
https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
在 C 语言中然后使用时,需要加上 struct 关键字:
为了让代码更加简洁,C 语言引入了 typedef,
允许我们为结构体类型定义一个别名,
这样就不需要每次都写 struct 关键字了:
// 使用时不再需要 struct
关键字
WNDCLASSA wc; // 直接使用别名
WNDCLASS WindowClass = {}; 是标准的 零初始化,将所有成员初始化为其类型的默认值(通常是 0 或 nullptr)。
WNDCLASS WindowClass = {0}; 将结构体的第一个成员初始化为 0,并且其他成员通常会被零初始化。
CS_CLASSDC
和 CS_OWNDC
是 Windows API 中 WNDCLASS
结构体的 style
成员的两个标志位,用来指定窗口类的设备上下文 (DC) 行为。
标志 | 描述 | 设备上下文类型 |
---|---|---|
CS_CLASSDC |
所有窗口共享一个设备上下文 (DC) | 共享 DC |
CS_OWNDC |
每个窗口都有自己的设备上下文 (DC) | 独立 DC |
CS_CLASSDC
适合需要共享设备上下文的情况。CS_OWNDC
适合需要每个窗口独立绘制的情况。
MainWindowCallback 函数是一个 窗口过程函数(Window Procedure),在 Windows 程序中,它用于处理与窗口相关的消息。
每当窗口接收到消息时(如用户输入、窗口大小变化、系统事件等),该函数就会被调用来处理这些消息。
处理窗体消息
cpp
// game.cpp : Defines the entry point for the application.
//
#include <windows.h>
LRESULT CALLBACK
MainWindowCallback(HWND hwnd, // 窗口句柄,表示消息来源的窗口
UINT Message, // 消息标识符,表示当前接收到的消息类型
WPARAM wParam, // 与消息相关的附加信息,取决于消息类型
LPARAM LParam) { // 与消息相关的附加信息,取决于消息类型
LRESULT Result; // 定义一个变量来存储消息处理的结果
switch (Message) { // 根据消息类型进行不同的处理
case WM_SIZE: { // 窗口大小发生变化时的消息
OutputDebugStringA("WM_SIZE\n"); // 输出调试信息,表示窗口大小已改变
} break;
case WM_DESTROY: { // 窗口销毁时的消息
OutputDebugStringA("WM_DESTROY\n"); // 输出调试信息,表示窗口正在销毁
} break;
case WM_CLOSE: { // 窗口关闭时的消息
OutputDebugStringA("WM_CLOSE\n"); // 输出调试信息,表示窗口正在关闭
} break;
case WM_ACTIVATEAPP: { // 应用程序激活或失去焦点时的消息
OutputDebugStringA(
"WM_ACTIVATEAPP\n"); // 输出调试信息,表示应用程序激活或失去焦点
} break;
default: { // 对于不处理的消息,调用默认的窗口过程
Result = DefWindowProc(hwnd, Message, wParam,
LParam); // 调用默认窗口过程处理消息
} break;
}
return Result; // 返回处理结果
}
int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, //
PSTR cmdline, int cmdshow) {
WNDCLASS WindowClass = {};
// 使用大括号初始化,所有成员都被初始化为零(0)或 nullptr
// WindowClass.style:表示窗口类的样式。通常设置为一些 Windows
// 窗口样式标志(例如 CS_HREDRAW, CS_VREDRAW)。
WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
// CS_HREDRAW 当窗口的宽度发生变化时,窗口会被重绘。
// CS_VREDRAW 当窗口的高度发生变化时,窗口会被重绘
// WindowClass.lpfnWndProc:指向窗口过程函数的指针,窗口过程用于处理与窗口相关的消息。
WindowClass.lpfnWndProc = MainWindowCallback;
// WindowClass.hInstance:指定当前应用程序的实例句柄,Windows
// 应用程序必须有一个实例句柄。
WindowClass.hInstance = hInst;
// WindowClass.lpszClassName:指定窗口类的名称,通常用于创建窗口时注册该类。
WindowClass.lpszClassName = "gameWindowClass"; // 类名
return 0;
}
注册窗口
注册窗口 成功后创建窗口
cpp
if (RegisterClass(&WindowClass)) { // 如果窗口类注册成功
HWND WindowHandle = CreateWindowEx(0, // 创建窗口,使用扩展窗口风格
WindowClass.lpszClassName, // 窗口类的名称,指向已注册的窗口类
"game", // 窗口标题(窗口的名称)
WS_OVERLAPPEDWINDOW | WS_VISIBLE, // 窗口样式:重叠窗口(带有菜单、边框等)并且可见
CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(X坐标)
CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(Y坐标)
CW_USEDEFAULT, // 窗口的初始宽度:使用默认宽度
CW_USEDEFAULT, // 窗口的初始高度:使用默认高度
0, // 父窗口句柄(此处无父窗口,传0)
0, // 菜单句柄(此处没有菜单,传0)
hInst, // 当前应用程序的实例句柄
0 // 额外的创建参数(此处没有传递额外参数)
);
// 如果窗口创建成功,WindowHandle 将保存窗口的句柄
} else { // 如果窗口类注册失败
// 这里可以处理注册失败的逻辑
// 比如输出错误信息,或退出程序等
}
如果窗口创建成功,启动一个无限循环,等待和处理消息
cpp
// 如果窗口创建成功,WindowHandle 将保存窗口的句柄
if (WindowHandle) { // 检查窗口句柄是否有效,若有效则进入消息循环
for (;;) { // 启动一个无限循环,等待和处理消息
MSG Message; // 声明一个 MSG 结构体,用于接收消息
BOOL MessageResult = GetMessage(&Message, // 获取下一个消息
0, // 目标窗口句柄,0表示从所有窗口获取消息
0, // 获取所有消息
0); // 获取所有消息
if (MessageResult > 0) { // 如果返回值大于 0,表示成功获取到消息
TranslateMessage(&Message); // 翻译消息,如果是键盘消息需要翻译
DispatchMessage(&Message); // 分派消息,调用窗口过程处理消息
} else { // 如果返回值为 0 或 -1,表示没有更多消息或发生了错误
break; // 跳出循环,结束消息循环
}
}
} else { // 如果窗口创建失败
// 这里可以处理窗口创建失败的逻辑
// 比如输出错误信息,或退出程序等
}
至此windows窗口创建成功
alt text
现在窗口不能关闭,能输出WM_SIZE,WM_ACTIVATEAPP 消息
绘制
cpp
case WM_PAINT: { // 处理 WM_PAINT 消息,通常在窗口需要重新绘制时触发
PAINTSTRUCT Paint; // 定义一个 PAINTSTRUCT 结构体,保存绘制的信息
// 调用 BeginPaint 开始绘制,并获取设备上下文 (HDC),同时填充 Paint 结构体
HDC DeviceContext = BeginPaint(hwnd, &Paint);
// 获取当前绘制区域的左上角坐标
int x = Paint.rcPaint.left;
int y = Paint.rcPaint.top;
// 计算绘制区域的宽度和高度
int Height = Paint.rcPaint.bottom - Paint.rcPaint.top;
int Width = Paint.rcPaint.right - Paint.rcPaint.left;
// 这里的宽度计算是错误的,应该是右边减去左边
// 使用 WHITENESS 操作符填充矩形区域为白色
PatBlt(DeviceContext, x, y, Width, Height, WHITENESS);
// 调用 EndPaint 结束绘制,并释放设备上下文
EndPaint(hwnd, &Paint);
} break;
cpp
PAINTSTRUCT 主要在 窗口绘制(painting) 过程中使用,特别是当窗口的部分区域需要重绘时(例如窗口被覆盖后再恢复)。BeginPaint 和 EndPaint 函数通常会使用 PAINTSTRUCT 来获取绘制所需的上下文信息。
typedef struct tagPAINTSTRUCT {
HDC hdc; // 设备上下文句柄,用于绘制窗口内容
BOOL fErase; // 是否清除背景,TRUE表示清除,FALSE表示不清除
RECT rcPaint; // 描述要更新的区域(矩形区域),窗口内容被绘制到该区域
BOOL fRestore; // 是否恢复被擦除区域的内容,通常为 TRUE 时表示恢复
BOOL fIncUpdate; // 是否为增量更新(更新的区域是变化部分)
BYTE rgbReserved[32]; // 保留字段,用于扩展结构体,通常不使用
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
//PatBlt 函数是 Windows 图形设备接口(GDI)的一部分,用于执行图形绘制操作,具体是填充矩形区域,且操作可以基于位图操作符(ROP)。以下是对 PatBlt 函数的详细解释:
cpp
WINGDIAPI BOOL WINAPI PatBlt(
_In_ HDC hdc, // 设备上下文句柄
_In_ int x, // 矩形区域的左上角 x 坐标
_In_ int y, // 矩形区域的左上角 y 坐标
_In_ int w, // 矩形的宽度
_In_ int h, // 矩形的高度
_In_ DWORD rop // 位图操作符
);
如果PatBlt 编译未定义链接Gdi32.lib
# 链接 User32.lib 库
target_link_libraries(game PRIVATE User32.lib Gdi32.lib)
设置填充颜色白色和黑色来回变换在刷新窗口的时候
cpp
static DWORD Operation = WHITENESS;
// 使用 WHITENESS 操作符填充矩形区域为白色
PatBlt(DeviceContext, x, y, Width, Height, Operation);
if (Operation == WHITENESS) {
Operation = BLACKNESS;
} else {
Operation = WHITENESS;
}