WinAPI与GDI+拖动PNG透明窗口异常问题求助
问题修复方案
问题1:窗口拖动时跳至左上角的修复
- 原因:坐标计算逻辑错误,且用于记录鼠标偏移的变量是局部变量,每次消息处理后会丢失值。
- 修复步骤:
- 将记录拖动起始状态的变量改为全局变量,确保跨消息处理时保留值。
- 在
WM_LBUTTONDOWN中记录屏幕坐标系下的初始鼠标位置和窗口位置。 - 在
WM_MOUSEMOVE中计算鼠标移动的增量,将增量应用到窗口初始位置得到新坐标,避免客户端坐标与屏幕坐标转换错误。
问题2:PNG透明区域显示垃圾内容的修复
- 原因:未使用分层窗口样式,系统无法正确处理PNG的逐像素透明,导致透明区域残留之前的窗口内容。
- 修复步骤:
- 使用
CreateWindowEx创建窗口并添加WS_EX_LAYERED扩展样式。 - 通过
UpdateLayeredWindow绘制PNG,让系统负责窗口的透明合成,确保透明区域正确显示桌面内容。 - 修正图片路径的转义字符(单反斜杠改为双反斜杠),避免图片加载失败。
- 使用
修复后的完整代码
#include <windows.h> #include <gdiplus.h> #pragma comment(lib, "gdiplus.lib") using namespace Gdiplus; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); Image* image; int imageWidth = 0; int imageHeight = 0; bool isDragging = false; POINT dragStartPos; RECT windowStartRect; // 替换原windowStartPos为RECT,记录窗口初始位置 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // 修正路径转义字符 image = Image::FromFile(L"C:\\Users\\hacktooth\\Downloads\\bg.png"); if (image) { imageWidth = image->GetWidth(); imageHeight = image->GetHeight(); } WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = NULL; wcex.lpszMenuName = NULL; wcex.lpszClassName = L"WindowClass"; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); RegisterClassEx(&wcex); // 使用CreateWindowEx添加WS_EX_LAYERED扩展样式 HWND hWnd = CreateWindowEx( WS_EX_LAYERED, L"WindowClass", L"PNG Image", WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, imageWidth, imageHeight, NULL, NULL, hInstance, NULL ); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } if (image) { delete image; } GdiplusShutdown(gdiplusToken); return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // 创建兼容内存DC用于绘制PNG HDC memDC = CreateCompatibleDC(hdc); HBITMAP hBitmap = CreateCompatibleBitmap(hdc, imageWidth, imageHeight); HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, hBitmap); // 将PNG绘制到内存DC Graphics graphics(memDC); graphics.DrawImage(image, 0, 0, imageWidth, imageHeight); // 准备UpdateLayeredWindow参数 POINT windowPos; GetWindowRect(hWnd, &windowStartRect); windowPos.x = windowStartRect.left; windowPos.y = windowStartRect.top; SIZE windowSize = { imageWidth, imageHeight }; POINT srcPos = { 0, 0 }; BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; // 更新分层窗口内容,处理透明 UpdateLayeredWindow(hWnd, NULL, &windowPos, &windowSize, memDC, &srcPos, 0, &blend, ULW_ALPHA); // 清理资源 SelectObject(memDC, oldBitmap); DeleteObject(hBitmap); DeleteDC(memDC); EndPaint(hWnd, &ps); } break; case WM_LBUTTONDOWN: isDragging = true; // 记录屏幕坐标系下的初始鼠标位置和窗口位置 GetCursorPos(&dragStartPos); GetWindowRect(hWnd, &windowStartRect); SetCapture(hWnd); break; case WM_LBUTTONUP: ReleaseCapture(); isDragging = false; break; case WM_MOUSEMOVE: if (isDragging) { POINT currentMousePos; GetCursorPos(¤tMousePos); // 计算鼠标移动增量 int deltaX = currentMousePos.x - dragStartPos.x; int deltaY = currentMousePos.y - dragStartPos.y; // 应用增量到窗口初始位置,移动窗口 SetWindowPos(hWnd, NULL, windowStartRect.left + deltaX, windowStartRect.top + deltaY, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
额外说明
- 分层窗口(
WS_EX_LAYERED)需要Windows 2000及以上系统支持,是处理逐像素透明的标准方案。 UpdateLayeredWindow会直接将内存DC的内容合成到桌面,无需处理WM_ERASEBKGND,避免透明区域残留垃圾。- 确保PNG图片路径正确,使用双反斜杠转义路径中的目录分隔符。
内容的提问来源于stack exchange,提问作者hacktooth
相关产品推荐
相关产品推荐

