Skip to content

信息学实验室作业(八)

更新: 2025/3/15 字数: 0 字 时长: 0 分钟

一、实验目的

本次实验聚焦于Win32API中定时器和鼠标处理功能的应用,旨在通过为之前绘制的图形(以房子为例)添加移动交互功能,加深对Win32API消息驱动机制、定时器操作以及鼠标事件处理的理解,提升基于Win32API进行图形化应用开发的实践能力。

二、实验环境

本次实验在Mac系统上,使用(如CrossOver、WineBottler等支持在Mac上进行Win32开发的工具)作为开发环境进行代码编写、编译和调试。

三、实现思路

  1. 初始化设置:在窗口创建阶段,定义全局变量来记录图形移动状态、移动偏移量和定时器ID。创建窗口并初始化相关资源,为后续操作做准备。
  2. 鼠标事件处理:在窗口过程函数中,针对鼠标左键按下(WM_LBUTTONDOWN)和右键按下(WM_RBUTTONDOWN)消息进行处理。左键按下时,启动定时器,使图形开始移动;右键按下时,停止定时器,图形停止移动。
  3. 定时器处理:利用SetTimer函数创建定时器,设置合适的时间间隔。在定时器触发的WM_TIMER消息处理函数中,根据移动状态更新图形的位置偏移量,并触发窗口重绘,实现图形的持续移动效果。
  4. 图形绘制与移动:在窗口重绘(WM_PAINT)消息处理函数中,根据图形移动状态和偏移量,绘制移动后的图形。在窗口销毁(WM_DESTROY)消息处理函数中,停止定时器,释放相关资源,确保程序正常结束。

四、代码实现

cpp
#include <windows.h>

// 全局变量
BOOL bShowHouse = FALSE;
BOOL bShowName = FALSE;
const char* developerName = "叶蓝骅";
BOOL bMoving = FALSE;
int moveX = 0;
int moveY = 0;
UINT_PTR timerId = 0;

// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE: {
        // 创建按钮
        CreateWindow("BUTTON", "显示图形", WS_VISIBLE | WS_CHILD, 10, 10, 100, 30, hwnd, (HMENU)1, NULL, NULL);
        CreateWindow("BUTTON", "清除图形", WS_VISIBLE | WS_CHILD, 120, 10, 100, 30, hwnd, (HMENU)2, NULL, NULL);
        CreateWindow("BUTTON", "显示姓名", WS_VISIBLE | WS_CHILD, 230, 10, 100, 30, hwnd, (HMENU)3, NULL, NULL);
        break;
    }
    case WM_COMMAND: {
        switch (LOWORD(wParam)) {
        case 1:
            bShowHouse = TRUE;
            bShowName = FALSE;
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        case 2:
            bShowHouse = FALSE;
            bShowName = FALSE;
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        case 3:
            bShowHouse = FALSE;
            bShowName = TRUE;
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        }
        break;
    }
    case WM_LBUTTONDOWN: {
        bMoving = TRUE;
        // 启动定时器,每20毫秒触发一次
        timerId = SetTimer(hwnd, 1, 20, NULL);
        break;
    }
    case WM_RBUTTONDOWN: {
        bMoving = FALSE;
        // 停止定时器
        if (timerId) {
            KillTimer(hwnd, timerId);
            timerId = 0;
        }
        break;
    }
    case WM_TIMER: {
        if (bMoving) {
            // 向右下移动
            moveX += 1;
            moveY += 1;
            InvalidateRect(hwnd, NULL, TRUE);
        }
        break;
    }
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        if (bShowHouse) {
            // 绘制屋顶(红色)
            HPEN hRedPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
            HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
            SelectObject(hdc, hRedPen);
            SelectObject(hdc, hRedBrush);
            POINT roofPoints[] = { {200 + moveX, 100 + moveY}, {100 + moveX, 200 + moveY}, {300 + moveX, 200 + moveY} };
            Polygon(hdc, roofPoints, 3);
            DeleteObject(hRedPen);
            DeleteObject(hRedBrush);

            // 绘制墙壁(蓝色)
            HPEN hBluePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
            HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));
            SelectObject(hdc, hBluePen);
            SelectObject(hdc, hBlueBrush);
            Rectangle(hdc, 100 + moveX, 200 + moveY, 300 + moveX, 300 + moveY);
            DeleteObject(hBluePen);
            DeleteObject(hBlueBrush);

            // 绘制烟囱(绿色)
            HPEN hGreenPen = CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
            HBRUSH hGreenBrush = CreateSolidBrush(RGB(0, 255, 0));
            SelectObject(hdc, hGreenPen);
            SelectObject(hdc, hGreenBrush);
            Rectangle(hdc, 250 + moveX, 50 + moveY, 270 + moveX, 150 + moveY);
            DeleteObject(hGreenPen);
            DeleteObject(hGreenBrush);
        }

        if (bShowName) {
            SetTextColor(hdc, RGB(0, 0, 0));
            SetBkMode(hdc, TRANSPARENT);
            TextOut(hdc, 200, 200, developerName, strlen(developerName));
        }

        EndPaint(hwnd, &ps);
        break;
    }
    case WM_DESTROY:
        if (timerId) {
            KillTimer(hwnd, timerId);
        }
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// 主函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    // 注册窗口类
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "HouseDrawing";
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    RegisterClass(&wc);

    // 创建窗口
    HWND hwnd = CreateWindow(wc.lpszClassName, "房子绘制", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 600, 400, NULL, NULL, hInstance, NULL);

    // 显示窗口
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    // 消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

五、代码解释

  1. 全局变量bShowHousebShowName用于控制房子图形和开发者姓名的显示;bMoving记录图形是否正在移动;moveXmoveY存储图形在x和y方向的移动偏移量;timerId保存定时器的标识符。
  2. 窗口过程函数(WndProc
    • WM_CREATE:窗口创建时,创建“显示图形”“清除图形”“显示姓名”三个按钮。
    • WM_COMMAND:根据按钮ID处理按钮点击事件,更新bShowHousebShowName的值,触发窗口重绘。
    • WM_LBUTTONDOWN:鼠标左键按下时,将bMoving设为TRUE,并启动定时器,设定定时器ID为1,时间间隔为20毫秒。
    • WM_RBUTTONDOWN:鼠标右键按下时,将bMoving设为FALSE,若定时器正在运行,则停止定时器。
    • WM_TIMER:定时器触发时,若bMovingTRUE,则使moveXmoveY分别增加1,实现图形向右下移动,并触发窗口重绘。
    • WM_PAINT:重绘窗口时,若bShowHouseTRUE,根据moveXmoveY的值绘制移动后的房子图形;若bShowNameTRUE,显示开发者姓名。
    • WM_DESTROY:窗口销毁时,停止定时器,发送退出消息,结束程序。
  3. 主函数(WinMain:注册窗口类,设置窗口过程函数、实例句柄、类名和背景颜色等;创建窗口并显示;进入消息循环,处理窗口消息。

六、编译与运行

  1. 编译:将代码保存为.cpp文件,在开发环境中进行编译。确保开发环境已正确配置Win32API相关的头文件和库文件路径。编译过程中,根据错误提示信息修改代码中的语法错误和逻辑错误,直至编译成功。
  2. 运行:编译成功后运行程序。程序启动后,窗口中显示三个按钮。点击“显示图形”按钮,房子图形出现;按下鼠标左键,房子开始向右下移动;按下鼠标右键,房子停止移动。点击“显示姓名”按钮,窗口中显示开发者姓名;点击“清除图形”按钮,房子图形和姓名消失。

七、实验总结

通过本次实验,成功实现了在Win32API环境下,利用定时器和鼠标事件为图形添加交互功能。在实验过程中,深入理解了定时器和鼠标消息的处理机制,以及如何通过消息驱动实现图形的动态效果和用户交互。遇到了定时器启动和停止的时机把握、图形绘制坐标计算等问题,通过调试和查阅资料解决了这些问题,提升了编程和调试能力。后续可以进一步拓展功能,如增加图形移动速度的控制、改变图形移动方向的更多操作方式等,以丰富应用的交互体验。