VC++ 窗口程序嘗試用雙緩衝消除閃爍,卻始終顯示白屏。核心代碼片段如下:
case WM_CREATE:
hCloudBMP = (HBITMAP)LoadImage(NULL, "1.bmp", IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
cloudDC = CreateCompatibleDC(hdc); // ❌ hdc 此時未初始化
SelectObject(cloudDC, hCloudBMP);
ReleaseDC(hWnd, hdc); // ❌ 過早釋放 hdc
hBgBMP = (HBITMAP)LoadImage(NULL, "bg.bmp", IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
bgDC = CreateCompatibleDC(hdc); // ❌ hdc 已被 ReleaseDC,句柄無效
SelectObject(bgDC, hBgBMP);
ReleaseDC(hWnd, hdc);
hMemDC = CreateCompatibleDC(hdc); // ❌ 致命:hdc 已失效,創建失敗返回 NULL
hTemp = CreateCompatibleBitmap(hdc, 1280, 720); // ❌ 同理失敗
SelectObject(hMemDC, hTemp);
ReleaseDC(hWnd, hdc);
SetTimer(hWnd, 100, 100, NULL);
break;
WM_CREATE 階段 hdc 尚未獲取(需在 WM_PAINT 中 BeginPaint 獲取,或用 GetDC)。ReleaseDC(hWnd, hdc) 過早釋放導致後續 CreateCompatibleDC(hdc) 參數無效,API 調用失敗返回 NULL。BitBlt 操作無效 DC,自然白屏。// 全局/靜態變量保存位圖與兼容 DC,不保存 hdc
static HBITMAP hCloudBMP, hBgBMP, hTemp;
static HDC cloudDC, bgDC, hMemDC;
static BITMAP bm, bgBm;
// WM_CREATE:只加載資源、創建兼容 DC(用屏幕 DC 或稍後在 WM_PAINT 獲取)
case WM_CREATE:
hCloudBMP = (HBITMAP)LoadImage(NULL, "1.bmp", IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
hBgBMP = (HBITMAP)LoadImage(NULL, "bg.bmp", IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
// 用屏幕 DC 創建兼容 DC,不依賴窗口 hdc
HDC hScreenDC = GetDC(NULL);
cloudDC = CreateCompatibleDC(hScreenDC);
bgDC = CreateCompatibleDC(hScreenDC);
hMemDC = CreateCompatibleDC(hScreenDC);
ReleaseDC(NULL, hScreenDC);
SelectObject(cloudDC, hCloudBMP);
SelectObject(bgDC, hBgBMP);
GetObject(hCloudBMP, sizeof(BITMAP), &bm);
GetObject(hBgBMP, sizeof(BITMAP), &bgBm);
// 創建與屏幕兼容的位圖作為後備緩衝
hTemp = CreateCompatibleBitmap(hScreenDC, 1280, 720); // 或用 GetSystemMetrics 獲取分辨率
SelectObject(hMemDC, hTemp);
SetTimer(hWnd, 100, 100, NULL);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// 1. 先將背景繪到內存 DC
BitBlt(hMemDC, 0, 0, bgBm.bmWidth, bgBm.bmHeight, bgDC, 0, 0, SRCCOPY);
// 2. 再繪制移動圖層
BitBlt(hMemDC, x, y, bm.bmWidth, bm.bmHeight, cloudDC, 0, 0, SRCCOPY);
// 3. 一次性將內存 DC 複制到窗口 DC
BitBlt(hdc, 0, 0, 1280, 720, hMemDC, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
DeleteDC(cloudDC); DeleteObject(hCloudBMP);
DeleteDC(bgDC); DeleteObject(hBgBMP);
DeleteDC(hMemDC); DeleteObject(hTemp);
KillTimer(hWnd, 100);
PostQuitMessage(0);
break;
每個 GDI API 必須檢查返回值,失敗時調用
GetLastError()定位錯誤碼。若作者在CreateCompatibleDC後檢查NULL並打印錯誤,兩三天的折騰可縮減至幾分鐘。
| 錯誤做法 | 正確做法 |
|---|---|
| WM_CREATE 用未初始化 hdc | 用 GetDC(NULL) 獲取屏幕 DC 創建兼容 DC |
| 過早 ReleaseDC 導致後續 API 失敗 | 兼容 DC/位圖創建完成後無需窗口 DC,生命周期獨立 |
| 不檢查 API 返回值 | 所有 GDI 調用必判 NULL/0,配合 GetLastError() |
| BitBlt 目標 DC 錯誤 | 內存 DC → 窗口 DC,單向流向清晰 |
按此修正,雙緩衝即可正常工作,告別白屏與閃爍。