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); } }
关键修改点说明
- 后台画布初始化:在
WM_CREATE里不再加载资源位图,而是创建和窗口尺寸完全匹配的兼容位图作为持久化画布,初始化背景为白色。 - 绘制目标转移:
draw_on_canvas现在操作内存DC(hdcCompat),所有绘制都存在内存位图里,不再直接碰窗口DC。 - 重绘同步:每次绘制后调用
InvalidateRect,通知系统触发WM_PAINT,把内存画布的内容同步到窗口。 - 资源清理:在
WM_DESTROY里手动删除所有创建的GDI对象(内存DC、位图、画刷),避免内存泄漏。 - 额外优化:去掉了没必要的
SS_BITMAP窗口样式(这是静态控件的样式,你的子窗口是自定义绘制,不需要)。
可选扩展:用状态数组持久化
如果你需要对每个格子的状态做逻辑处理(比如批量修改、保存配置),可以用上你定义的compared_entries数组:
- 每次绘制时更新数组(比如
compared_entries[索引] = true代表红色,false代表白色) WM_PAINT时遍历数组,根据状态重绘每个格子
这种方式适合需要逻辑控制的场景,而内存位图适合快速直观的绘制场景,你可以根据需求选择。




