Windows API窗口程序设计作业报告

一、项目概述

本次小组作业基于 Visual Studio 2022 和 C++ 开发了一个桌面窗口应用程序。作业要求程序具有灰色背景,窗口居中显示指定文字,单次播放背景音乐,并可通过按键动态改变窗口样式和鼠标指针形状。

具体实现功能如下:

  1. 窗口初始状态为标准样式(包含最小化、最大化、关闭按钮),背景色为灰色。
  2. 窗口客户区居中显示文字“大家好,这是我的第一个Windows API程序!”。
  3. 程序启动时异步单次播放背景音乐。
  4. 初始状态下鼠标进入窗口指针为标准箭头。
  5. 按下键盘空格键后,窗口样式改变为仅有标题栏和关闭按钮,同时鼠标指针变为手型。

二、效果展示

请在此处插入两张程序运行截图进行对比。

image

Screenshot 2026-09-22 230215

三、核心技术点解析

1. 窗口背景与初始设置

在注册窗口类时,使用 GetStockObject(GRAY_BRUSH) 将窗口背景设置为灰色。同时,将 wc.hCursor 设为 NULL,以便后续通过 WM_SETCURSOR 消息完全接管鼠标指针的动态变化。

2. 居中文本绘制

在 WM_PAINT 消息中,使用 DrawText 函数,配合 DT_CENTER | DT_VCENTER | DT_SINGLELINE 标志,并设置 SetBkMode(hdc, TRANSPARENT) 保证文字背景透明,从而实现文字的完美居中。

3. 单次异步播放背景音乐

引入 <mmsystem.h> 并链接 winmm.lib。在 WM_CREATE 中使用 PlaySound 函数。为了实现单次播放且不阻塞主程序运行,参数设置为 SND_FILENAME | SND_ASYNC。

4. 动态修改窗口样式与鼠标指针

在 WM_KEYDOWN 中监听空格键。按下空格后:

  • 使用 SetWindowLongPtr 获取并修改窗口的 GWL_STYLE,通过位运算剔除 WS_THICKFRAME、WS_MINIMIZEBOX 和 WS_MAXIMIZEBOX,保留 WS_CAPTION 和 WS_SYSMENU。
  • 调用 SetWindowPos 并附加 SWP_FRAMECHANGED 强制系统重绘非客户区。
  • 改变全局状态变量 g_bIsHandCursor 为 TRUE,并调用 SetCursor 立即更新指针。
  • 配合 WM_SETCURSOR 消息,当鼠标处于 HTCLIENT 区域时,根据全局状态动态设置光标(初始为 IDC_ARROW,改变后为 IDC_HAND)。

四、开发问题与解决记录

  1. 链接错误 LNK2019 和 LNK1120
    由于Visual Studio默认创建的是控制台应用程序,链接器会寻找main函数而不是WinMain。在遇到属性页配置未生效的情况下,最终采用在代码顶部添加 #pragma comment(linker, "/SUBSYSTEM:WINDOWS") 直接指挥链接器,顺利解决了外部符号无法解析的错误。

  2. 鼠标指针动态变化失效
    最初尝试在按键时调用 SetCursor,但鼠标移动后会被 WNDCLASS 中预设的光标覆盖。查阅 MSDN 后,改为拦截 WM_SETCURSOR 消息,判断 LOWORD(lParam) == HTCLIENT 时动态设置光标,彻底解决了该问题。

  3. 窗口样式改变无效
    发现在最大化状态下修改窗口样式看不出变化。解决方案是在修改前先调用 ShowWindow(hWnd, SW_RESTORE) 还原窗口,同时使用 SetWindowLongPtr 代替旧版的 SetWindowLong,确保了在64位系统下的兼容性。

五、完整源代码

#include <windows.h>
#include <mmsystem.h>

#pragma comment(lib, "winmm.lib")
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")

BOOL g_bIsHandCursor = FALSE; 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
    wc.lpszClassName = L"FirstWin32App";
    wc.hCursor = NULL; 

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK | MB_ICONERROR);
        return 0;
    }

    int screenW = GetSystemMetrics(SM_CXSCREEN);
    int screenH = GetSystemMetrics(SM_CYSCREEN);
    int winW = 800, winH = 600;
    int posX = (screenW - winW) / 2;
    int posY = (screenH - winH) / 2;

    HWND hWnd = CreateWindow(
        L"FirstWin32App",
        L"Windows API程序设计",
        WS_OVERLAPPEDWINDOW, 
        posX, posY, winW, winH,
        NULL, NULL, hInstance, NULL
    );

    if (hWnd == NULL) {
        return 0;
    }

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

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

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message) {
    case WM_CREATE:
        PlaySound(L"bgm.wav", NULL, SND_FILENAME | SND_ASYNC);
        break;

    case WM_SETCURSOR:
        if (LOWORD(lParam) == HTCLIENT) {
            if (g_bIsHandCursor) {
                SetCursor(LoadCursor(NULL, IDC_HAND));
            } else {
                SetCursor(LoadCursor(NULL, IDC_ARROW));
            }
            return TRUE;
        }
        break;

    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        
        RECT rect;
        GetClientRect(hWnd, &rect);
        
        SetBkMode(hdc, TRANSPARENT);
        SetTextColor(hdc, RGB(0, 0, 0));
        DrawText(hdc, L"大家好,这是我的第一个Windows API程序!", -1, &rect, 
                 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        
        EndPaint(hWnd, &ps);
        break;
    }

    case WM_KEYDOWN:
        if (wParam == VK_SPACE) {
            ShowWindow(hWnd, SW_RESTORE);

            LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
            style &= ~(WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
            SetWindowLongPtr(hWnd, GWL_STYLE, style);
            
            SetWindowPos(hWnd, NULL, 0, 0, 0, 0, 
                         SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

            g_bIsHandCursor = TRUE;
            SetCursor(LoadCursor(NULL, IDC_HAND)); 
        }
        break;

    case WM_DESTROY:
        PlaySound(NULL, NULL, 0);
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
posted @ 2026-09-22 23:13  m0rt1s  阅读(2)  评论(0)    收藏  举报