You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinAPI与GDI+拖动PNG透明窗口异常问题求助

问题修复方案

问题1:窗口拖动时跳至左上角的修复

  • 原因:坐标计算逻辑错误,且用于记录鼠标偏移的变量是局部变量,每次消息处理后会丢失值。
  • 修复步骤:
    1. 将记录拖动起始状态的变量改为全局变量,确保跨消息处理时保留值。
    2. 在WM_LBUTTONDOWN中记录屏幕坐标系下的初始鼠标位置和窗口位置。
    3. 在WM_MOUSEMOVE中计算鼠标移动的增量,将增量应用到窗口初始位置得到新坐标,避免客户端坐标与屏幕坐标转换错误。

问题2:PNG透明区域显示垃圾内容的修复

  • 原因:未使用分层窗口样式,系统无法正确处理PNG的逐像素透明,导致透明区域残留之前的窗口内容。
  • 修复步骤:
    1. 使用CreateWindowEx创建窗口并添加WS_EX_LAYERED扩展样式。
    2. 通过UpdateLayeredWindow绘制PNG,让系统负责窗口的透明合成,确保透明区域正确显示桌面内容。
    3. 修正图片路径的转义字符(单反斜杠改为双反斜杠),避免图片加载失败。

修复后的完整代码

#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(&currentMousePos);
            // 计算鼠标移动增量
            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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 02:27:04