C++中如何撤销GDI+直接在屏幕上完成的绘制内容?
解决方案:快速清除屏幕上的GDI+绘制内容
直接通过GetDC(0)绘制到屏幕DC的内容属于临时覆盖,系统不会追踪这些绘制操作,因此需要针对性地刷新绘制区域,或者改用分层窗口作为绘制载体来简化清除逻辑。
方法一:记录绘制区域并针对性刷新
核心思路是保存所有绘制图形的边界区域,清除时通知系统重新刷新这些区域,让底层窗口重绘覆盖你的绘制内容。
步骤1:记录绘制区域
在绘制时,把每个图形的边界矩形保存下来(注意笔宽的影响,比如3像素的笔需要把矩形向外扩展2像素以覆盖完整线条):
#include <vector> #include <windows.h> #include <gdiplus.h> using namespace Gdiplus; // 保存所有绘制区域的列表(建议作为全局或类成员变量) std::vector<RECT> g_drawnRegions; // 绘制代码示例 void DrawOnScreen() { HDC hScreenDC = GetDC(0); Graphics graphics(hScreenDC); Pen gp(Color::DarkOliveGreen, 3); SolidBrush gpb(Color::DarkOliveGreen); // 绘制第一个椭圆并记录区域 Rect ellipse1(matpos[0][0] - 5, matpos[0][1] - 5, 10, 10); graphics.FillEllipse(&gpb, ellipse1); g_drawnRegions.push_back({ellipse1.X, ellipse1.Y, ellipse1.X + ellipse1.Width, ellipse1.Y + ellipse1.Height}); // 绘制第二个椭圆并记录区域 Rect ellipse2(matpos[1][0] - 5, matpos[1][1] - 5, 10, 10); graphics.FillEllipse(&gpb, ellipse2); g_drawnRegions.push_back({ellipse2.X, ellipse2.Y, ellipse2.X + ellipse2.Width, ellipse2.Y + ellipse2.Height}); // 绘制矩形并记录区域(扩展笔宽范围) Rect rect = get_rect_from_points(Rect(matpos[0][0], matpos[0][1], matpos[1][0], matpos[1][1])); graphics.DrawRectangle(&gp, rect); RECT rectRegion = { rect.X - 2, rect.Y - 2, rect.X + rect.Width + 2, rect.Y + rect.Height + 2 }; g_drawnRegions.push_back(rectRegion); ReleaseDC(0, hScreenDC); }
步骤2:清除绘制内容
遍历保存的区域,调用RedrawWindow强制刷新这些区域:
void ClearAllDrawings() { for (const RECT& rc : g_drawnRegions) { // 通知系统立即刷新指定区域,让底层窗口重绘 RedrawWindow(NULL, &rc, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); } g_drawnRegions.clear(); // 清空记录 }
方法二:改用分层窗口作为绘制载体
直接绘制到屏幕DC的方式缺乏可控性,改用分层透明窗口作为绘制容器,清除时只需刷新或隐藏窗口即可,逻辑更简洁稳定。
步骤1:创建分层窗口
// 注册窗口类(程序初始化时执行) void RegisterOverlayWindowClass(HINSTANCE hInstance) { WNDCLASSEX wc = {0}; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = DefWindowProc; wc.hInstance = hInstance; wc.lpszClassName = L"OverlayWindowClass"; RegisterClassEx(&wc); } // 创建分层窗口 HWND CreateOverlayWindow(HINSTANCE hInstance) { HWND hWnd = CreateWindowEx( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST, L"OverlayWindowClass", NULL, WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL ); // 设置窗口背景为完全透明(颜色键为黑色) SetLayeredWindowAttributes(hWnd, RGB(0,0,0), 0, LWA_COLORKEY); ShowWindow(hWnd, SW_SHOW); return hWnd; }
步骤2:在分层窗口上绘制
// 绘制到分层窗口 void DrawOnOverlay(HWND hOverlayWnd) { HDC hWndDC = GetDC(hOverlayWnd); Graphics graphics(hWndDC); Pen gp(Color::DarkOliveGreen, 3); SolidBrush gpb(Color::DarkOliveGreen); // 绘制逻辑和之前一致 graphics.FillEllipse(&gpb, matpos[0][0] - 5, matpos[0][1] - 5, 10, 10); graphics.FillEllipse(&gpb, matpos[1][0] - 5, matpos[1][1] - 5, 10, 10); graphics.DrawRectangle(&gp, get_rect_from_points(Rect(matpos[0][0], matpos[0][1], matpos[1][0], matpos[1][1]))); ReleaseDC(hOverlayWnd, hWndDC); }
步骤3:清除绘制内容
只需刷新窗口(背景透明,刷新后绘制内容消失)或直接隐藏窗口:
void ClearOverlayDrawings(HWND hOverlayWnd) { // 方式1:刷新窗口,清除所有绘制 InvalidateRect(hOverlayWnd, NULL, TRUE); UpdateWindow(hOverlayWnd); // 方式2:直接隐藏窗口(退出前销毁即可) // ShowWindow(hOverlayWnd, SW_HIDE); }
为什么之前的方法无效?
- 覆盖透明元素:透明画刷不会修改屏幕像素,因此无法覆盖原有绘制内容。
- 直接调用RedrawWindow(NULL, NULL, ...):刷新整个屏幕效率极低,且系统可能不会立即处理,导致你的绘制区域未被及时覆盖。
内容的提问来源于stack exchange,提问作者How2Code
相关产品推荐
相关产品推荐

