Windows API窗口绘图程序设计
实验效果截图说明
截图描述:
窗口背景为白色;在窗口内单击鼠标左键,会在点击位置生成一个红色填充正方形,正方形边长随机在 10~100 像素之间;多次点击可以叠加绘制多个红色方块,窗口缩放、最小化还原后,所有绘制的正方形不会消失。
一、实验思路
实验目的
使用 Win32 API 编写 Windows 桌面图形程序,掌握 Windows 窗口消息机制、GDI 绘图、鼠标消息捕获、随机数生成。实现需求:在窗口任意位置按下鼠标左键,绘制边长范围在 10~100 的红色填充正方形。
设计思路
- 窗口创建:注册窗口类,创建 800×600 的窗口,启动消息循环,等待系统消息。
- 数据存储:自定义结构体保存每个正方形的左上角坐标、边长;用数组存储多个正方形信息,支持多次点击绘制多个图形。
- 鼠标消息捕获:监听
WM_LBUTTONDOWN(鼠标左键按下消息),获取鼠标点击坐标,生成 10~100 的随机边长,存入数组。 - GDI 绘图重绘:调用
InvalidateRect()触发窗口重绘消息WM_PAINT;在重绘消息中创建红色画刷,循环遍历数组,使用Rectangle函数绘制并填充所有正方形。 - 资源管理:绘图结束释放 GDI 画刷资源,避免内存泄漏。
- 窗口销毁:监听
WM_DESTROY消息,退出消息循环,关闭程序。
实现原理
Windows 程序是消息驱动模型。程序不主动轮询鼠标,而是等待操作系统发送消息。当鼠标左键按下,系统发送WM_LBUTTONDOWN;窗口需要重绘(窗口打开、窗口移动、调用 InvalidateRect)时触发WM_PAINT,在这个消息里完成绘图。GDI(图形设备接口)负责窗口绘图,画刷决定图形填充颜色。
二、完整源代码
#include <Windows.h>
#include <cstdlib>
#include <ctime>
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
struct RectInfo
{
int x;
int y;
int width;
};
RectInfo rectList[1000];
int rectCount = 0;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
srand((unsigned int)time(NULL));
const wchar_t CLASS_NAME[] = L"MyWinRectClass";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"窗口注册失败!", L"Error", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
HWND hWnd = CreateWindowEx(
0,
CLASS_NAME,
L"随机红色正方形",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInstance, NULL
);
if (hWnd == NULL)
{
MessageBox(NULL, L"窗口创建失败!", L"Error", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_LBUTTONDOWN:
{
if (rectCount >= 1000) break;
int mouseX = LOWORD(lParam);
int mouseY = HIWORD(lParam);
int side = rand() % 91 + 10;
rectList[rectCount].x = mouseX;
rectList[rectCount].y = mouseY;
rectList[rectCount].width = side;
rectCount++;
InvalidateRect(hWnd, NULL, TRUE);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hRedBrush);
for (int i = 0; i < rectCount; i++)
{
int x1 = rectList[i].x;
int y1 = rectList[i].y;
int x2 = x1 + rectList[i].width;
int y2 = y1 + rectList[i].width;
Rectangle(hdc, x1, y1, x2, y2);
}
SelectObject(hdc, hOldBrush);
DeleteObject(hRedBrush);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
三、主要代码详细解析
1. 头文件
#include <Windows.h>
#include <cstdlib>
#include <ctime>
Windows.h:Win32 API 核心头文件,提供窗口、消息、GDI 绘图全部函数与数据类型。cstdlib:rand () 随机数函数。ctime:time () 获取系统时间,作为随机数种子。
2. 结构体 RectInfo
struct RectInfo
{
int x;
int y;
int width;
};
RectInfo rectList[1000];
int rectCount = 0;
作用:保存每一个正方形的数据。
因为窗口每次重绘时,画面会清空,必须把所有正方形信息保存起来,每次 WM_PAINT 全部重新绘制。
rectList数组存放所有方块,rectCount记录当前方块总数。
3. WinMain 程序入口
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
Win32 窗口程序入口。
hInstance:当前程序实例句柄。srand((unsigned int)time(NULL)):随机种子,保证每次运行随机边长不一样。
窗口注册:WNDCLASSEX 结构体描述窗口类的属性,包含消息处理函数、光标、背景色,使用RegisterClassEx向操作系统注册窗口类。
CreateWindowEx:创建窗口实例,设置窗口标题、大小、样式。
消息循环 while(GetMessage):Windows 程序核心。不断读取系统消息,交给 WndProc 处理,程序持续运行直到收到退出消息。
4. WndProc 窗口回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
窗口消息回调函数,操作系统有事件(鼠标点击、窗口重绘、关闭)就调用这个函数,msg 代表消息类型。
① WM_LBUTTONDOWN 鼠标左键按下
int mouseX = LOWORD(lParam);
int mouseY = HIWORD(lParam);
int side = rand() % 91 + 10;
- lParam 参数打包了鼠标坐标,
LOWORD取低 16 位 = X 坐标;HIWORD取高 16 位 = Y 坐标。 rand()%91:得到 0~90,+10→ 范围 10~100,满足题目边长要求。InvalidateRect(hWnd, NULL, TRUE):请求窗口重绘,触发 WM_PAINT 消息。
② WM_PAINT 绘图消息
HDC hdc = BeginPaint(hWnd, &ps);
HBRUSH hRedBrush = CreateSolidBrush(RGB(255,0,0));
SelectObject(hdc, hRedBrush);
Rectangle(hdc, x1,y1,x2,y2);
- HDC:设备上下文句柄,窗口绘图的画布。
CreateSolidBrush(RGB(255,0,0)):创建红色实心画刷,绘制图形会自动填充内部。Rectangle(hdc, x1,y1,x2,y2):绘制矩形,x1,y1 左上角,x2,y2 右下角;因为宽高相等,绘制结果就是正方形。DeleteObject(hRedBrush):GDI 资源必须手动释放,否则会内存泄漏。
③ WM_DESTROY
窗口点击关闭按钮,触发此消息,调用PostQuitMessage(0),退出消息循环,程序结束。
四、实验结果
- 编译运行,弹出 800×600 窗口,标题为随机红色正方形。
- 在窗口任意位置单击鼠标左键,点击位置生成红色填充正方形,边长随机 10~100。
- 多次点击,生成多个大小各不相同红色正方形。
- 窗口最小化后恢复、拖动窗口改变大小,所有正方形仍然保留。
- 点击窗口关闭按钮,程序正常退出。
五、小组分工与心得体会
组员1(负责:代码编写、功能实现、代码合作调试)
分工:完成 Win32 窗口框架代码编写,实现鼠标消息捕获、随机正方形生成、GDI 绘图;调试程序,保证程序正常运行。
心得体会:本次实验学习了 Windows 消息驱动编程模型,一开始对 WinMain、WndProc 消息回调很难理解,最开始遇到 LNK2019 找不到 main 的链接错误,通过修改VS项目为Windows窗口程序解决。明白了窗口绘图不是永久画在屏幕上,每次窗口重绘都要重新绘制图形,所以必须保存所有图形的数据。掌握 GDI 画刷、Rectangle 绘图函数,理解鼠标消息的获取方式,体会到 Windows 程序事件驱动的特点。
组员2(负责:实验思路梳理、代码注释、文档撰写、代码合作调试)
分工:梳理实验设计思路,阅读源码,为代码添加详细注释;撰写实验文档,整理代码解析部分,整理博客文章结构,梳理每个函数的作用。
心得体会:在阅读 Win32 代码过程中,理解 Windows 图形程序的运行逻辑。原来程序不是顺序执行,而是被动等待操作系统发送消息。通过整理文档,把消息循环、WM_LBUTTONDOWN、WM_PAINT 这些概念梳理清楚。认识到写代码时规范注释的重要性,方便团队成员看懂代码,同时加深自己对 GDI 绘图原理的理解。
组员3(负责:程序测试、截图、代码合作调试)
分工:运行程序进行多组测试,测试边界情况(连续多次点击、窗口缩放、最小化还原),截取程序运行效果图;检查程序是否满足题目要求。
心得体会:本次作业测试中,测试了多次连续点击生成大量正方形,验证数组上限;测试窗口缩放后图形不会丢失。熟悉了 VS 项目创建流程,区分控制台项目与 Windows 桌面项目的区别。以前只写控制台 C++ 程序,第一次接触图形窗口编程,感受到图形编程的魅力,也发现图形程序需要考虑资源释放、消息重绘等控制台程序不会遇到的问题。
六、实验小结
本次实验基于 Win32 API 实现 Windows 消息驱动图形程序,完成鼠标左键点击窗口生成随机大小红色填充正方形的功能。实验搭建标准 Win32 窗口框架,利用结构体数组存储每一个正方形的坐标与边长信息,通过捕获WM_LBUTTONDOWN鼠标左键消息获取点击位置,生成 10~100 像素的随机边长并保存图形数据;调用InvalidateRect触发WM_PAINT重绘消息,在重绘函数中使用 GDI 画刷完成所有正方形的绘制,同时做好 GDI 资源释放,防止内存泄漏。
实验验证了 Windows 消息驱动机制:程序不会主动轮询输入,而是等待操作系统分发消息;窗口画面无法永久保存,当窗口缩放、最小化还原时会触发重绘,因此必须持久化图形数据,每次重绘时重新绘制全部图形。开发过程中遇到 Windows 子系统配置错误引发的 LNK2019 链接问题,通过修改项目属性解决。小组分工完成代码编写调试、文档整理注释、功能测试与截图工作。
通过本次实验,掌握 Win32 窗口创建、消息循环、回调函数、鼠标消息捕获与 GDI 基础绘图;理解 GDI 资源管理要点,区分控制台程序与 Windows 窗口程序开发差异。同时认识到图形程序开发需要兼顾数据存储、消息响应、资源回收。


浙公网安备 33010602011771号