You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinAPI经典应用中基于HDC的图形定时刷新实现方案问询

问题描述

需求

绘制一个正方形,并使其颜色每0.1秒左右变化一次。

尝试情况

  • 用InvalidateRect搭配UpdateWindow,仅在WndProc的default分支能勉强运行,但颜色只变化几次就停止,还会导致对话框变形、菜单部分消失;
  • 若不放在default分支,InvalidateRect仅在点击操作时触发,无法实现0.1秒自动刷新;
  • 曾在WinMain主消息循环中手动发送WM_PAINT消息,无效果;
  • 考虑将主窗口句柄hWnd设为全局变量,但不确定是否合理。

当前代码

主消息循环代码

while (windowState)
{
    BOOL msgState;

    iter_Count++;
    if (iter_Count >= 10) {
        SendMessage(nullptr, WM_COMMAND, (WPARAM)NULL, (LPARAM)NULL);
        DispatchMessage(&msg);
        iter_Count = 0;
    }

    else {
        msgState = PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE);

        if (msgState) {
            windowState = GetMessage(&msg, nullptr, 0, 0);
            msgState = false;
        }
        else {
            //msg.message = WM_PAINT; Tried this but it also doesn't work
           // DispatchMessage(&msg);
        }

        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    } 
}

窗口过程代码

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        case ID_SETTINGS_SETPALETTE:
            res = true;
            DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, set_Palette);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
case WM_PAINT:
    {   
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
            updater.updateImage(hdc);
        EndPaint(hWnd, &ps);
    }
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    InvalidateRect(hWnd, NULL, TRUE);//this screws up the dialog boxes and works maybe twice
    UpdateWindow(hWnd);
    return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

咨询

实现该需求的最优方式是什么?


解决方案

最优方案是使用Windows原生定时器(SetTimer),这是系统专门为周期性任务设计的机制,比手动在消息循环里计数稳定得多,不会干扰正常消息处理流程。

具体步骤

  1. 创建定时器
    在窗口初始化时(比如WM_CREATE消息分支)设置定时器,间隔设为100毫秒(对应0.1秒):
case WM_CREATE:
    // 定时器ID设为1,间隔100ms
    SetTimer(hWnd, 1, 100, NULL);
    break;
  1. 处理定时消息
    在WndProc中添加WM_TIMER分支,每次定时触发时刷新窗口:
case WM_TIMER:
    if (wParam == 1) { // 匹配定时器ID
        InvalidateRect(hWnd, NULL, TRUE);
        // 可选:如果需要立即刷新可加UpdateWindow(hWnd),否则系统会自动处理WM_PAINT
    }
    break;
  1. 销毁定时器
    窗口销毁前(WM_DESTROY分支)销毁定时器,避免资源泄漏:
case WM_DESTROY:
    KillTimer(hWnd, 1); // 销毁ID为1的定时器
    PostQuitMessage(0);
    break;

原有方案问题解析

  1. 在WndProc的default分支调用InvalidateRect会让几乎所有系统消息都触发刷新,严重干扰对话框、菜单等控件的正常消息处理,导致界面异常;
  2. 手动在消息循环计数的方式不可靠,消息循环的执行速度不固定,还会被阻塞消息打断,定时精度无法保证;
  3. 手动发送WM_PAINT无效,该消息由系统维护,必须通过InvalidateRect触发系统生成,而非手动发送。

关于全局hWnd的问题

使用定时器方案不需要将hWnd设为全局变量,WM_CREATE和WM_TIMER消息中都能直接拿到当前窗口的hWnd参数。如果确实需要全局访问窗口句柄,只要确保窗口创建后正确赋值,也是合理的,但定时器方案完全不需要这么做。

内容的提问来源于stack exchange,提问作者Vroom ZOOM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:12:06