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

如何解决使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:21:57