如何解决使用STM_SETIMAGE更新位图时的窗口闪烁问题
解决Common Controls v6下STM_SETIMAGE更新位图的闪烁问题
问题根源
启用Common Controls v6后,静态控件的重绘逻辑发生变化:每次调用STM_SETIMAGE时,控件会先执行背景擦除操作,再绘制新位图,两步操作的时间差导致视觉闪烁;同时原代码中先删除旧位图再创建新位图的顺序,会让控件短暂处于无位图状态,进一步加剧闪烁。
解决方法
以下是几种递进的解决方案,可根据实际场景选择:
1. 优化位图替换顺序,避免控件无位图状态
原代码先删除旧位图再创建新位图,会导致控件短暂空白。调整为先创建新位图,再替换旧位图,最后删除旧位图:
void Redraw() { HBITMAP newBitmap = NULL; u8* RenderImageData = (u8*)malloc(512 * 512 * 4); u32* p = (u32*)RenderImageData; // 生成位图数据 for (int i = 0; i < 512; i++) { p[i + counter * 512] = 0xff000000; p[i * 512 + counter] = 0xff000000; } // 创建新位图 newBitmap = CreateBitmap(512, 512, 4, 8, RenderImageData); free(RenderImageData); if (newBitmap) { // 替换位图并获取旧位图句柄 HBITMAP oldBitmap = (HBITMAP)SendMessage(logo, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)newBitmap); // 仅删除旧位图,避免误删新位图 if (oldBitmap && oldBitmap != testBitmap) { DeleteObject(oldBitmap); } testBitmap = newBitmap; } counter = (counter + 1) & 511; }
2. 阻止控件自动擦除背景
给静态控件添加SS_WHITERECT样式,或子类化控件拦截WM_ERASEBKGND消息,避免系统自动擦除背景:
// 修改静态控件创建代码 logo = CreateWindow("Static", NULL, SS_BITMAP | WS_CHILD | WS_VISIBLE | SS_WHITERECT, 40, 40, 512, 512, m_hwnd, NULL, NULL, NULL);
若窗口背景非白色,建议子类化控件拦截WM_ERASEBKGND:
WNDPROC oldStaticProc; LRESULT CALLBACK StaticSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ERASEBKGND: // 跳过背景擦除,消除闪烁根源 return TRUE; default: return CallWindowProc(oldStaticProc, hwnd, message, wParam, lParam); } } // 在创建logo控件后添加子类化代码 oldStaticProc = (WNDPROC)SetWindowLongPtr(logo, GWLP_WNDPROC, (LONG_PTR)StaticSubclassProc);
3. 启用双缓冲绘制(彻底解决闪烁)
子类化静态控件,自行实现WM_PAINT的双缓冲绘制,完全控制重绘流程:
WNDPROC oldStaticProc; LRESULT CALLBACK StaticSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ERASEBKGND: return TRUE; // 禁止系统擦除背景 case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HBITMAP hBitmap = (HBITMAP)SendMessage(hwnd, STM_GETIMAGE, IMAGE_BITMAP, 0); if (hBitmap) { // 创建内存DC作为缓冲 HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP oldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap); BITMAP bmInfo; GetObject(hBitmap, sizeof(bmInfo), &bmInfo); // 一次性将缓冲位图绘制到窗口 BitBlt(hdc, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, hdcMem, 0, 0, SRCCOPY); // 清理资源 SelectObject(hdcMem, oldBitmap); DeleteDC(hdcMem); } EndPaint(hwnd, &ps); return 0; } default: return CallWindowProc(oldStaticProc, hwnd, message, wParam, lParam); } } // 在App_Initialize中创建logo后添加子类化 oldStaticProc = (WNDPROC)SetWindowLongPtr(logo, GWLP_WNDPROC, (LONG_PTR)StaticSubclassProc);
4. 启用窗口组合扩展样式
给主窗口添加WS_EX_COMPOSITED扩展样式,让Windows使用双缓冲绘制整个窗口:
m_hwnd = CreateWindowEx(WS_EX_COMPOSITED, TEXT("myApp"), TEXT("Simple Set image Example"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 640, 640, NULL, NULL, HINST_THISCOMPONENT, 0);
完整修改后的代码示例
#include <windows.h> #include <CommCtrl.h> #include <math.h> #include <objbase.h> #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #ifndef HINST_THISCOMPONENT EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) #endif HWND logo; HWND m_hwnd; HBITMAP testBitmap = NULL; int counter = 0; WNDPROC oldStaticProc; typedef unsigned char u8; typedef unsigned int u32; LRESULT CALLBACK StaticSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ERASEBKGND: return TRUE; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HBITMAP hBitmap = (HBITMAP)SendMessage(hwnd, STM_GETIMAGE, IMAGE_BITMAP, 0); if (hBitmap) { HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP oldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap); BITMAP bmInfo; GetObject(hBitmap, sizeof(bmInfo), &bmInfo); BitBlt(hdc, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, oldBitmap); DeleteDC(hdcMem); } EndPaint(hwnd, &ps); return 0; } default: return CallWindowProc(oldStaticProc, hwnd, message, wParam, lParam); } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hwnd, message, wParam, lParam); } HRESULT App_Initialize() { HRESULT hr; WNDCLASSEX wcex = { sizeof(WNDCLASSEX) }; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = HINST_THISCOMPONENT; wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.lpszClassName = TEXT("myApp"); RegisterClassEx(&wcex); m_hwnd = CreateWindowEx(WS_EX_COMPOSITED, TEXT("myApp"), TEXT("Simple Set image Example"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 640, 640, NULL, NULL, HINST_THISCOMPONENT, 0); hr = m_hwnd ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { ShowWindow(m_hwnd, SW_SHOWNORMAL); UpdateWindow(m_hwnd); } logo = CreateWindow("Static", NULL, SS_BITMAP | WS_CHILD | WS_VISIBLE, 40, 40, 512, 512, m_hwnd, NULL, NULL, NULL); oldStaticProc = (WNDPROC)SetWindowLongPtr(logo, GWLP_WNDPROC, (LONG_PTR)StaticSubclassProc); return hr; } void Redraw() { HBITMAP newBitmap = NULL; u8* RenderImageData = (u8*)malloc(512 * 512 * 4); u32* p = (u32*)RenderImageData; for (int i = 0; i < 512; i++) { p[i + counter * 512] = 0xff000000; p[i * 512 + counter] = 0xff000000; } newBitmap = CreateBitmap(512, 512, 4, 8, RenderImageData); free(RenderImageData); if (newBitmap) { HBITMAP oldBitmap = (HBITMAP)SendMessage(logo, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)newBitmap); if (oldBitmap && oldBitmap != testBitmap) { DeleteObject(oldBitmap); } testBitmap = newBitmap; } counter = (counter + 1) & 511; } int WINAPI WinMain(HINSTANCE hInstance ,HINSTANCE hPrevInstance ,LPSTR lpCmdLine ,int nCmdShow) { if (SUCCEEDED(CoInitialize(NULL))) { if (SUCCEEDED(App_Initialize())) { MSG msg; while(1) { while (PeekMessage(&msg, NULL, 0,0,PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } Sleep(10); Redraw(); } } CoUninitialize(); } return 0; }
内容的提问来源于stack exchange,提问作者user3162134
相关产品推荐
相关产品推荐

