You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

GDI32窗口还原后无法重绘绘制内容,求助解决最小化后内容丢失问题

GDI32窗口还原后无法重绘绘制内容,求助解决最小化后内容丢失问题

兄弟,你的问题我太熟悉了——现在你是直接把像素画在窗口的临时设备上下文(HDC)上,这部分内容是一次性的,窗口只要一触发重绘(比如最小化还原、被其他窗口遮挡后恢复),系统就会清空窗口区域,而你的WM_PAINT里只刷了初始加载的位图,自然之前手动画的内容就全丢了。

核心问题根源

你没有把用户的绘制操作持久化到一个后台画布上,而是直接操作窗口的临时DC。窗口DC的内容是不稳定的,系统会在重绘时覆盖它。解决办法就是用GDI的「内存DC+兼容位图」组合,把所有绘制都存在这个内存画布上,每次WM_PAINT就把这个画布复制到窗口DC,这样无论怎么重绘,内容都能保留。

修改后的完整代码(标注重点修改)

我已经帮你调整了关键部分,你可以直接替换原有代码测试:

#include <windows.h>
#include <stdbool.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DrawShapeWndProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hinst;

#define PIXEL_SIZE 30
#define PIXEL_PER_ROW 7
#define PIXEL_PER_COL 9
bool compared_entries[PIXEL_PER_ROW * PIXEL_PER_COL];

// 注意:现在改为操作传入的目标DC(内存DC),而非窗口DC
void draw_on_canvas(HDC hdcTarget, LPARAM lParam, HBRUSH brush) {
    RECT rcClient;
    HRGN bgRgn;
    int new_x, new_y;
    POINT pt;

    pt.x = (LONG) LOWORD(lParam);
    pt.y = (LONG) HIWORD(lParam);

    new_x = (pt.x / PIXEL_SIZE) * PIXEL_SIZE;
    new_y = (pt.y / PIXEL_SIZE) * PIXEL_SIZE;

    rcClient.left = new_x;
    rcClient.top = new_y;
    rcClient.right = new_x + PIXEL_SIZE;
    rcClient.bottom = new_y + PIXEL_SIZE;

    bgRgn = CreateRectRgnIndirect(&rcClient);
    FillRgn(hdcTarget, bgRgn, brush);
    DeleteObject(bgRgn); // 必须手动删除GDI对象,避免内存泄漏
}

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    HWND hwnd, hwnd_draw_shape;
    MSG msg;
    WNDCLASS wc, wc2;

    hinst = hinstance;

    // 主窗口类注册
    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"MaWinClass";
    if (!RegisterClass(&wc)) return FALSE;

    // 绘图子窗口类注册
    wc2.style = 0;
    wc2.lpfnWndProc = DrawShapeWndProc;
    wc2.cbClsExtra = 0;
    wc2.cbWndExtra = 0;
    wc2.hInstance = hinstance;
    wc2.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc2.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc2.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc2.lpszMenuName = NULL;
    wc2.lpszClassName = L"DrawShapeClass";
    if (!RegisterClass(&wc2)) return FALSE;

    // 创建主窗口
    hwnd = CreateWindow(L"MaWinClass", L"Titre", WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT, 400, 450,
                        NULL, NULL, hinstance, NULL);
    if (!hwnd) return FALSE;

    // 创建绘图子窗口(修正:最后一个参数传hinstance,而非NULL)
    hwnd_draw_shape = CreateWindow(L"DrawShapeClass", NULL, 
                                   WS_VISIBLE | WS_CHILD | WS_BORDER,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 
                                   PIXEL_PER_ROW * PIXEL_SIZE, 
                                   PIXEL_PER_COL * PIXEL_SIZE,
                                   hwnd, NULL, hinstance, NULL);
    if (!hwnd_draw_shape) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_CREATE:
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

LRESULT CALLBACK DrawShapeWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    static HDC hdcCompat;    // 持久化的内存DC
    static HBITMAP hbmp;     // 内存DC对应的兼容位图(后台画布)
    static HBRUSH H_Brush_Red, H_Brush_White;

    switch (uMsg) {
        case WM_CREATE: {
            hdc = GetDC(hwnd);
            // 1. 创建兼容DC
            hdcCompat = CreateCompatibleDC(hdc);
            // 2. 创建和窗口尺寸一致的兼容位图(后台画布)
            int canvasW = PIXEL_PER_ROW * PIXEL_SIZE;
            int canvasH = PIXEL_PER_COL * PIXEL_SIZE;
            hbmp = CreateCompatibleBitmap(hdc, canvasW, canvasH);
            // 3. 把位图选入内存DC,初始化画布背景为白色
            SelectObject(hdcCompat, hbmp);
            RECT rcInit = {0,0,canvasW,canvasH};
            FillRect(hdcCompat, &rcInit, (HBRUSH)GetStockObject(WHITE_BRUSH));

            // 初始化画刷
            H_Brush_Red = CreateSolidBrush(RGB(255,0,0));
            H_Brush_White = CreateSolidBrush(RGB(255,255,255));
            
            ReleaseDC(hwnd, hdc);
            return 0;
        }

        case WM_LBUTTONDOWN:
            // 绘制到内存DC(持久化)
            draw_on_canvas(hdcCompat, lParam, H_Brush_Red);
            // 通知窗口重绘,把内存画布同步到窗口
            InvalidateRect(hwnd, NULL, FALSE);
            return 0;

        case WM_RBUTTONDOWN:
            draw_on_canvas(hdcCompat, lParam, H_Brush_White);
            InvalidateRect(hwnd, NULL, FALSE);
            return 0;

        case WM_MOUSEMOVE:
            if ((wParam & MK_LBUTTON)) {
                draw_on_canvas(hdcCompat, lParam, H_Brush_Red);
                InvalidateRect(hwnd, NULL, FALSE);
            }
            if ((wParam & MK_RBUTTON)) {
                draw_on_canvas(hdcCompat, lParam, H_Brush_White);
                InvalidateRect(hwnd, NULL, FALSE);
            }
            return 0;

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            // 把内存画布的内容复制到窗口DC
            BitBlt(hdc, 0, 0, 
                   PIXEL_PER_ROW*PIXEL_SIZE, 
                   PIXEL_PER_COL*PIXEL_SIZE, 
                   hdcCompat, 0, 0, SRCCOPY);
            EndPaint(hwnd, &ps);
            return 0;

        case WM_DESTROY:
            // 清理GDI资源,避免泄漏
            if (hdcCompat) {
                SelectObject(hdcCompat, GetStockObject(WHITE_BRUSH));
                DeleteDC(hdcCompat);
            }
            if (hbmp) DeleteObject(hbmp);
            DeleteObject(H_Brush_Red);
            DeleteObject(H_Brush_White);
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

关键修改点说明

  1. 后台画布初始化:在WM_CREATE里不再加载资源位图,而是创建和窗口尺寸完全匹配的兼容位图作为持久化画布,初始化背景为白色。
  2. 绘制目标转移draw_on_canvas现在操作内存DC(hdcCompat),所有绘制都存在内存位图里,不再直接碰窗口DC。
  3. 重绘同步:每次绘制后调用InvalidateRect,通知系统触发WM_PAINT,把内存画布的内容同步到窗口。
  4. 资源清理:在WM_DESTROY里手动删除所有创建的GDI对象(内存DC、位图、画刷),避免内存泄漏。
  5. 额外优化:去掉了没必要的SS_BITMAP窗口样式(这是静态控件的样式,你的子窗口是自定义绘制,不需要)。

可选扩展:用状态数组持久化

如果你需要对每个格子的状态做逻辑处理(比如批量修改、保存配置),可以用上你定义的compared_entries数组:

  • 每次绘制时更新数组(比如compared_entries[索引] = true代表红色,false代表白色)
  • WM_PAINT时遍历数组,根据状态重绘每个格子

这种方式适合需要逻辑控制的场景,而内存位图适合快速直观的绘制场景,你可以根据需求选择。

火山引擎 最新活动