首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Windows 桌面開發:控制繪圖刷新間隔的正確姿勢(Sleep + 消息泵)

     編輯:程式設計綜合問答

問題背景

初學 Windows GDI 繪圖,用 for 循環改變圓的座標實現動畫,但畫面要么閃爍嚴重,要么卡死不動,完全不知道如何控制幀率。

核心誤區

  • 單純 Sleep 會阻塞消息泵:窗口無法處理 WM_PAINT、WM_TIMER、移動/縮放等消息,導致「未響應」
  • 缺少雙緩衝:直接在 WM_PAINT 繪圖必然閃爍
  • 動畫邏輯不應在 WM_PAINT 內:繪製與狀態更新應分離

正確架構:定時器驅動 + 雙緩衝繪製

1. 使用 SetTimer 替代循環

// 創建窗口時設置定時器,約 60 FPS (16ms)
SetTimer(hwnd, TIMER_ANIMATION, 16, NULL);

// WM_TIMER 處理
case WM_TIMER:
    if (wParam == TIMER_ANIMATION) {
        UpdateCirclePosition();  // 更新邏輯座標
        InvalidateRect(hwnd, NULL, FALSE); // 請求重繪
    }
    break;

2. 雙緩衝消除閃爍

case WM_PAINT: {
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
    
    // 1. 創建兼容 DC 與位圖
    HDC memDC = CreateCompatibleDC(hdc);
    RECT rect; GetClientRect(hwnd, &rect);
    HBITMAP hBmp = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
    HBITMAP hOldBmp = SelectObject(memDC, hBmp);
    
    // 2. 在記憶體 DC 繪製(背景清除 + 圓)
    FillRect(memDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
    Ellipse(memDC, circle.x - circle.r, circle.y - circle.r, 
                   circle.x + circle.r, circle.y + circle.r);
    
    // 3. 一次性 BitBlt 到屏幕 DC
    BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
    
    // 4. 清理
    SelectObject(memDC, hOldBmp);
    DeleteObject(hBmp);
    DeleteDC(memDC);
    
    EndPaint(hwnd, &ps);
    return 0;
}

3. 如果必須用循環(教學演示),正確的消息泵集成

// 僅供教學理解消息機制,實際專案請用 SetTimer
for (int frame = 0; frame < totalFrames; frame++) {
    UpdateCirclePosition();
    InvalidateRect(hwnd, NULL, FALSE);
    
    // 關鍵:處理待處理消息,保持窗口響應
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    Sleep(16); // 粗略控制幀率
}

關鍵點:PeekMessage 非阻塞檢查消息隊列,PM_REMOVE 取出並移除,DispatchMessage 分發給窗口過程,這樣 WM_PAINT 才能被及時處理。

進階:高精度幀率控制

// 使用 QueryPerformanceCounter 實現精準幀率
LARGE_INTEGER freq, lastTime, currentTime;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&lastTime);

const double targetFrameTime = 1.0 / 60.0; // 60 FPS

while (running) {
    QueryPerformanceCounter(¤tTime);
    double elapsed = (double)(currentTime.QuadPart - lastTime.QuadPart) / freq.QuadPart;
    
    if (elapsed >= targetFrameTime) {
        UpdateCirclePosition();
        InvalidateRect(hwnd, NULL, FALSE);
        
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
        lastTime = currentTime;
    } else {
        // 精確睡眠剩餘時間(需考慮 Sleep 精度 ~1ms)
        DWORD sleepMs = (DWORD)((targetFrameTime - elapsed) * 1000);
        if (sleepMs > 1) Sleep(sleepMs);
    }
}

現代替代方案

技術優勢適用場景
Direct2D硬體加速、原生雙緩衝、高效能Win32 原生高效能繪圖
WPF / WinUI 3保留模式、聲明式動畫、GPU 加速.NET 桌面新專案
SDL2 / SFML跨平台、遊戲級循環、音頻輸入整合遊戲/多媒體應用
Skia / Cairo跨平台 2D 繪圖引擎自定義渲染管線

總結

Windows 繪圖動畫的正確姿勢:定時器驅動邏輯更新 → InvalidateRect 請求重繪 → WM_PAINT 雙緩衝繪製。初學階段理解 PeekMessage + Sleep 消息泵機制有助於掌握 Windows 事件驅動本質,但實際專案請拥抱 SetTimer 或現代圖形 API。記住:繪製與邏輯分離,消息泵不可阻塞,雙緩衝消除閃爍,三條鐵律走遍天下。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved