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

拖动GDI应用窗口顶部区域时窗口丢失问题求助

问题分析与解决方案

窗口拖动丢失问题

你遇到的窗口拖动顶部区域时丢失的问题,根源在于:

  • 自定义拖动逻辑未处理静态标题控件的鼠标消息,点击标题控件区域时,主窗口无法捕获鼠标事件,导致拖动逻辑失效。
  • 原拖动逻辑中WM_LBUTTONDOWN里的if (xClick < 0 && yClick < 0)判断完全无效,客户区坐标不可能为负,反而可能干扰正常流程。

修复方案:使用系统原生拖动逻辑(推荐)

在WndProc中添加WM_NCHITTEST消息处理,让顶部区域(包括标题控件)被识别为标题栏,由系统负责拖动,既稳定又不会影响动画:

case WM_NCHITTEST:
{
    POINT pt = { LOWORD(lParam), HIWORD(lParam) };
    ScreenToClient(hwnd, &pt);
    // 判定顶部30像素范围内为可拖动区域
    if (pt.y < 30)
    {
        return HTCAPTION;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

添加后,点击顶部任意区域(包括静态标题)都能直接拖动窗口,不会出现窗口丢失的情况。

星场动画卡顿问题

动画卡顿的原因是starfield函数每次绘制都重复创建内存DC、位图和刷子,频繁的GDI对象创建销毁会严重影响性能。

修复方案:缓存GDI资源

修改starfield函数,将内存DC和位图改为静态变量,仅初始化一次,避免重复创建:

void starfield(HDC hdc, int x, int y, RECT *rc)
{
    static HDC hdcMem = NULL;
    static HBITMAP hbmMem = NULL;
    static HBITMAP hbmOld = NULL;
    const int width = 335;
    const int height = 215;

    // 修正绘制区域坐标
    rc->left = x;
    rc->top = y;
    rc->right = x + width;
    rc->bottom = y + height;

    // 仅初始化一次GDI资源
    if (!hdcMem)
    {
        hdcMem = CreateCompatibleDC(hdc);
        hbmMem = CreateCompatibleBitmap(hdc, width, height);
        hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
    }

    // 填充黑色背景
    HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
    FillRect(hdcMem, rc, brush);
    DeleteObject(brush);

    // 绘制并更新星星位置
    for (int i = 0; i < NUM_STARS; i++)
    {
        SetPixel(hdcMem, stars[i].x, stars[i].y, RGB(255, 255, 255));
        stars[i].x = (stars[i].x + stars[i].speed) % width;
    }

    // 将内存位图绘制到窗口
    BitBlt(hdc, x, y, width, height, hdcMem, 0, 0, SRCCOPY);
}

同时在WM_DESTROY中添加资源清理,避免GDI泄漏:

case WM_DESTROY:
    // 清理缓存的GDI资源
    if (hdcMem)
    {
        SelectObject(hdcMem, hbmOld);
        DeleteObject(hbmMem);
        DeleteDC(hdcMem);
    }
    PostQuitMessage(EXIT_SUCCESS);
    return 0;

额外优化项

  1. 修复静态控件文字不可见问题:原WM_CTLCOLORSTATIC仅返回黑色刷子,但文字默认黑色,导致无法显示,修改为:
case WM_CTLCOLORSTATIC:
{
    HDC hdcStatic = (HDC)wParam;
    SetTextColor(hdcStatic, RGB(255, 255, 255)); // 设置白色文字
    SetBkColor(hdcStatic, RGB(0, 0, 0)); // 设置黑色背景
    return (LRESULT)GetStockObject(BLACK_BRUSH); // 使用系统刷子避免泄漏
}
  1. 移除无用全局变量:删除全局的PAINTSTRUCT ps和HDC hdc,WM_PAINT中已定义局部变量,全局变量无意义且可能引发冲突。

完整修改后代码

#include <Windows.h>
#include <iostream>
#include <windowsx.h>

#define IDB_EXIT 102

int WIDTH = 350;
int HEIGHT = 400;

const int NUM_STARS = 100;
struct Star
{
    int x, y;
    int speed;
};
Star stars[NUM_STARS];

void starfield(HDC hdc, int x, int y, RECT *rc)
{
    static HDC hdcMem = NULL;
    static HBITMAP hbmMem = NULL;
    static HBITMAP hbmOld = NULL;
    const int width = 335;
    const int height = 215;

    rc->left = x;
    rc->top = y;
    rc->right = x + width;
    rc->bottom = y + height;

    if (!hdcMem)
    {
        hdcMem = CreateCompatibleDC(hdc);
        hbmMem = CreateCompatibleBitmap(hdc, width, height);
        hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
    }

    HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
    FillRect(hdcMem, rc, brush);
    DeleteObject(brush);

    for (int i = 0; i < NUM_STARS; i++)
    {
        SetPixel(hdcMem, stars[i].x, stars[i].y, RGB(255, 255, 255));
        stars[i].x = (stars[i].x + stars[i].speed) % width;
    }
    BitBlt(hdc, x, y, width, height, hdcMem, 0, 0, SRCCOPY);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
    for (int i = 0; i < NUM_STARS; i++)
    {
        stars[i].x = rand() % 340;
        stars[i].y = rand() % 249;
        stars[i].speed = rand() % 3 + 1;
    }

    TCHAR appname[] = TEXT("Template");
    WNDCLASS wndclass;
    MSG msg;
    HWND hwnd;

    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hInstance = hInstance;
    wndclass.lpfnWndProc = WndProc;
    wndclass.lpszClassName = appname;
    wndclass.lpszMenuName = NULL;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("Window class is not registered"), TEXT("Error...."), MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_COMPOSITED,
                          appname,
                          appname,
                          WS_VISIBLE | WS_SYSMENU | WS_POPUP,
                          100,
                          100,
                          WIDTH,
                          HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    SetTimer(hwnd, 1, 10, NULL);
    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static RECT starfield_rc;

    switch (message)
    {
    case WM_CREATE:
    {
        HINSTANCE hIns = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
        HWND exit_button = CreateWindow(TEXT("BUTTON"), NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
                                   250, 360, 70, 30, hwnd, (HMENU)100, hIns, 0);
        HBITMAP exitImg = (HBITMAP)LoadImageA(GetModuleHandleA(nullptr), (LPCSTR)MAKEINTRESOURCE(IDB_EXIT), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

        HWND title = CreateWindow(TEXT("STATIC"), TEXT("Hello"), WS_CHILD | WS_VISIBLE, 150, 5, 120, 20, hwnd, 0, hIns, 0);

        SendMessageW(exit_button, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)exitImg);
    }
    break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        starfield(hdc, 6, 31, &starfield_rc);
        EndPaint(hwnd, &ps);
        break;
    }

    case WM_TIMER:
        InvalidateRect(hwnd, &starfield_rc, FALSE);
        break;

    case WM_COMMAND:
        if (wParam == 100)
        {
            PostQuitMessage(EXIT_SUCCESS);
            return 0;
        }
        break;

    case WM_CTLCOLORSTATIC:
    {
        HDC hdcStatic = (HDC)wParam;
        SetTextColor(hdcStatic, RGB(255, 255, 255));
        SetBkColor(hdcStatic, RGB(0, 0, 0));
        return (LRESULT)GetStockObject(BLACK_BRUSH);
    }

    case WM_NCHITTEST:
    {
        POINT pt = { LOWORD(lParam), HIWORD(lParam) };
        ScreenToClient(hwnd, &pt);
        if (pt.y < 30)
        {
            return HTCAPTION;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    case WM_DESTROY:
        if (hdcMem)
        {
            SelectObject(hdcMem, hbmOld);
            DeleteObject(hbmMem);
            DeleteDC(hdcMem);
        }
        PostQuitMessage(EXIT_SUCCESS);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

内容的提问来源于stack exchange,提问作者iamwho

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 19:43:10