Windows API绘图程序设计实验报告

一、实验目的

学习Windows-API窗口绘图编程,掌握Win32窗口程序中鼠标消息处理与GDI 绘图的基本方法,能够响应鼠标左键单击消息,在窗口客户区指定位置绘制具有随机边长的红色填充正方形。

二、实验内容与设计思想

设计一个简单的Windows窗口程序:在程序窗口内任意位置按下鼠标左键时,以该点为左上角绘制一个边长在10~100之间随机取值的红色填充正方形,且已绘制的正方形在窗口重绘后仍然保留。

设计思想:自定义 Square 结构体记录每个正方形的左上角坐标与边长,使用动态数组(realloc 扩容)保存所有正方形;WM_LBUTTONDOWN 消息中读取鼠标坐标并用 rand() 生成随机边长,同时做边界裁剪保证正方形完整落在客户区内;WM_PAINT 消息中创建红色实心画刷,遍历数组逐个 FillRect 重绘,从而实现多次点击累积绘制与窗口重绘不丢图。

三、实验使用环境

操作系统:Windows11

编程环境:Visual Studio2022

四、实验步骤和调试过程

源代码:

1. 头文件、数据结构与动态数组

#include <windows.h>
#include <stdlib.h>

typedef struct {
    int x;
    int y;
    int size;
} Square;

Square* g_pSquares = NULL;
int     g_nSquareCount = 0;
int     g_nSquareCapacity = 0;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

void AddSquare(int x, int y, int size)
{
    if (g_nSquareCount >= g_nSquareCapacity)
    {
        g_nSquareCapacity = (g_nSquareCapacity == 0) ? 16 : g_nSquareCapacity * 2;
        g_pSquares = (Square*)realloc(g_pSquares, g_nSquareCapacity * sizeof(Square));
    }
    g_pSquares[g_nSquareCount].x = x;
    g_pSquares[g_nSquareCount].y = y;
    g_pSquares[g_nSquareCount].size = size;
    g_nSquareCount++;
}

2. WinMain:注册窗口类、创建窗口、消息循环

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    srand(GetTickCount());

    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = TEXT("DrawSquareClass");

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("Failed to register window class"), TEXT("Error"), MB_ICONERROR);
        return FALSE;
    }

    HWND hWnd = CreateWindow(
        TEXT("DrawSquareClass"),
        TEXT("Win32 Draw Red Squares Lab"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        800, 600,
        NULL, NULL, hInstance, NULL);

    if (!hWnd)
    {
        MessageBox(NULL, TEXT("Failed to create window"), TEXT("Error"), MB_ICONERROR);
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    free(g_pSquares);
    return (int)msg.wParam;
}

3. 窗口过程:鼠标左键消息处理

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_LBUTTONDOWN:
    {
        int mouseX = LOWORD(lParam);
        int mouseY = HIWORD(lParam);
        int size = 10 + rand() % 91;

        RECT rcClient;
        GetClientRect(hWnd, &rcClient);
        if (mouseX + size > rcClient.right)   mouseX = rcClient.right - size;
        if (mouseY + size > rcClient.bottom) mouseY = rcClient.bottom - size;
        if (mouseX < 0) mouseX = 0;
        if (mouseY < 0) mouseY = 0;

        AddSquare(mouseX, mouseY, size);
        InvalidateRect(hWnd, NULL, TRUE);
        break;
    }

4. 窗口过程:重绘消息与销毁处理

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));

        for (int i = 0; i < g_nSquareCount; i++)
        {
            RECT rc;
            rc.left   = g_pSquares[i].x;
            rc.top    = g_pSquares[i].y;
            rc.right  = g_pSquares[i].x + g_pSquares[i].size;
            rc.bottom = g_pSquares[i].y + g_pSquares[i].size;
            FillRect(hdc, &rc, hBrush);
        }
        DeleteObject(hBrush);
        EndPaint(hWnd, &ps);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

效果截图:

image

程序运行效果:在窗口客户区内多次按下鼠标左键,即绘制出若干边长随机(10~100 像素)、红色实心填充的正方形。

五、实验小结

黄一华

遇到的问题:最初把绘图代码直接写在WM_LBUTTONDOWN里,点击后正方形能出现,但只要拖动窗口或被别的窗口遮挡再移开,所有正方形就全部消失。

解决方法:查阅资料后明白 WM_PAINT 负责"按需重绘",于是改为点击时只记录坐标和边长到数组,真正绘制统一放到 WM_PAINT 中遍历数组完成,遮挡后图形不再丢失。

吴雨桦

遇到的问题:一开始写 rand()%100,结果边长偏小且每次运行程序画出的正方形大小序列完全一样;另外 lParam 高低位取坐标也弄反过。

解决方法:用 srand(GetTickCount()) 做随机数种子解决重复问题;边长改为 10 + rand()%91 保证落在 [10,100];坐标用 LOWORD(lParam) 取 x、HIWORD(lParam) 取 y。

宫静茹

遇到的问题:一开始用 Rectangle 画出来的是红色边框、内部是白底;后来改成 FillRect 又忘记释放画刷,程序久运行后GDI对象句柄不断增多。

解决方法:改用 CreateSolidBrush(RGB(255,0,0)) 创建实心红刷,配合 FillRect 填充整个矩形;每次用完在WM_PAINT末尾调用 DeleteObject(hBrush) 释放资源。

胡磊

遇到的问题:点击靠近窗口右边缘或下边缘时,正方形的一部分会跑到客户区外面看不到;刚开始编译时还因为缺少stdlib.h报realloc未定义。

解决方法:在点击处理中先用 GetClientRect 取客户区大小,若 mouseX+size 超出右边界就把mouseX左移、若mouseY+size超出下边界就上移,并对负值坐标归零;补上 #include <stdlib.h> 后编译通过。

小组心得:本次Windows-API窗口绘图实验,掌握了Win32消息驱动编程、鼠标消息处理与GDI绘图的基础用法。程序不能直接在鼠标消息中绘图,需要保存图形数据,依靠WM_PAINT消息完成重绘,防止窗口遮挡后图形丢失。实验过程出现随机数重复、鼠标坐标读取错误、GDI画刷未释放、方块越界、头文件缺失等问题,经过调试全部解决。认识到GDI资源必须手动释放,交互程序需要做好边界条件判断。通过排查代码错误,加深了对Windows窗口编程原理的理解,提高了代码调试排错能力。

posted @ 2026-09-25 13:50  吴雨桦  阅读(6)  评论(0)    收藏  举报