如何防止C++屏幕绘图程序中绘制的像素消失?
问题原因
你遇到的像素消失问题,本质是Windows的窗口重绘机制导致的:当屏幕区域被其他窗口覆盖再移开时,系统会要求该区域所属的窗口重新绘制内容,但你的程序只是通过SetPixel临时在屏幕DC上画像素,既没有保存所有绘制的像素信息,也没有处理重绘逻辑,所以之前画的内容会被系统清除。
解决方案
要解决这个问题,你需要:
- 持久化存储所有绘制的像素数据:不仅记录坐标,还要记录每个坐标对应的颜色和笔触大小,确保重绘时能完整还原内容。
- 创建自定义绘图窗口:用一个全屏透明的顶层窗口承载绘图内容,响应系统的
WM_PAINT重绘消息,在需要时重新绘制所有保存的像素。 - 用窗口消息替代全局钩子:窗口可以直接处理鼠标和键盘事件,逻辑更简洁,无需额外的全局钩子。
修改后的完整代码
#include <Windows.h> #include <iostream> #include <fstream> #include <vector> #include <string> // 存储每个绘制像素的坐标、颜色和笔触大小 struct DrawPixel { int x; int y; COLORREF color; int penSize; // 0=细笔,1=中粗笔,2=粗笔 }; std::vector<DrawPixel> drawHistory; // 所有绘制的像素历史记录 COLORREF currentColor = RGB(255, 255, 255); // 当前绘图颜色 int currentPenSize = 0; // 默认细笔 bool isDrawing = false; // 是否处于绘图状态(左键按下) // 写入坐标到文件(保留原有逻辑) void WriteCoordinateToFile(int x, int y) { if (x >= 0 && x <= GetSystemMetrics(SM_CXSCREEN) && y >= 0 && y <= GetSystemMetrics(SM_CYSCREEN)) { std::ofstream outputFile("coordinates.txt", std::ios::app); if (outputFile.is_open()) { outputFile << x << " " << y << std::endl; outputFile.close(); } else { std::cerr << "无法打开文件!" << std::endl; } } } // 根据笔触大小绘制区域 void DrawPen(HDC hdc, int x, int y, COLORREF color, int penSize) { switch (penSize) { case 2: // 粗笔(x键) for (int i = -10; i <= 10; ++i) { for (int j = -10; j <= 10; ++j) { SetPixel(hdc, x + i, y + j, color); } } break; case 1: // 中粗笔(t键) for (int i = -2; i <= 2; ++i) { for (int j = -2; j <= 2; ++j) { SetPixel(hdc, x + i, y + j, color); } } break; default: // 细笔 SetPixel(hdc, x, y, color); break; } } // 窗口消息处理函数 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_MOUSEMOVE: { if (!isDrawing) break; int x = LOWORD(lParam); int y = HIWORD(lParam); POINT pt = {x, y}; ClientToScreen(hwnd, &pt); HDC hdc = GetDC(hwnd); DrawPen(hdc, x, y, currentColor, currentPenSize); ReleaseDC(hwnd, hdc); // 保存绘制记录 drawHistory.push_back({x, y, currentColor, currentPenSize}); WriteCoordinateToFile(pt.x, pt.y); break; } case WM_LBUTTONDOWN: { isDrawing = true; SetCapture(hwnd); // 捕获鼠标,移出窗口也能继续绘图 break; } case WM_LBUTTONUP: { isDrawing = false; ReleaseCapture(); break; } case WM_KEYDOWN: { switch (wParam) { case 't': case 'T': currentPenSize = currentPenSize == 1 ? 0 : 1; std::cout << (currentPenSize == 1 ? "开启中粗笔" : "切换为细笔") << std::endl; break; case 'x': case 'X': currentPenSize = currentPenSize == 2 ? 0 : 2; std::cout << (currentPenSize == 2 ? "开启粗笔" : "切换为细笔") << std::endl; break; case 'g': currentColor = RGB(0, 255, 0); std::cout << "已选择绿色" << std::endl; break; case 'G': currentColor = RGB(0, 76, 0); std::cout << "已选择深绿色" << std::endl; break; case 'r': currentColor = RGB(255, 0, 0); std::cout << "已选择红色" << std::endl; break; case 'R': currentColor = RGB(76, 0, 0); std::cout << "已选择深红色" << std::endl; break; case 'b': currentColor = RGB(0, 0, 255); std::cout << "已选择蓝色" << std::endl; break; case 'B': currentColor = RGB(0, 0, 76); std::cout << "已选择深蓝色" << std::endl; break; default: currentColor = RGB(255, 255, 255); std::cout << "未知按键,切换为默认白色" << std::endl; break; } break; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 重绘所有历史记录 for (const auto& pixel : drawHistory) { DrawPen(hdc, pixel.x, pixel.y, pixel.color, pixel.penSize); } EndPaint(hwnd, &ps); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } int main() { // 注册窗口类 const wchar_t CLASS_NAME[] = L"ScreenDrawClass"; WNDCLASS wc = {0}; wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandle(NULL); wc.lpszClassName = CLASS_NAME; wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); // 透明背景 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); // 创建全屏透明顶层窗口 HWND hwnd = CreateWindowEx( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST, CLASS_NAME, L"屏幕绘图", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, GetModuleHandle(NULL), NULL ); // 设置窗口透明、鼠标穿透 SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA); ShowWindow(hwnd, SW_SHOW); SetFocus(hwnd); // 获取键盘焦点 // 消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
关键改动说明
- 绘制数据持久化:用
vector<DrawPixel>存储所有绘制细节,确保重绘时能完整恢复之前的绘图内容。 - 自定义全屏窗口:窗口设置为透明、顶层,既不影响其他窗口操作,又能作为绘图载体响应重绘消息。
- 消息驱动替代钩子:窗口直接处理鼠标和键盘事件,逻辑更简洁,避免全局钩子带来的复杂度。
内容的提问来源于stack exchange,提问作者00prf00
相关产品推荐
相关产品推荐

