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

自定义最大化按钮悬停触发Windows Snap辅助菜单问题

自定义最大化按钮触发Windows 11 Snap布局菜单的修复方案

1. 调整窗口样式,移除系统默认标题栏

现有代码使用的WS_OVERLAPPEDWINDOW包含系统标题栏和边框,会干扰自定义标题栏的消息处理。创建窗口时需改用基础样式,保留必要功能但移除默认标题栏渲染:

HWND hwnd = CreateWindowEx(
    0,
    CLASS_NAME,
    L"Test Window",
    WS_POPUPWINDOW | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL, NULL, hInstance, NULL
);
  • WS_POPUPWINDOW配合WS_CAPTION保证窗口拥有非客户端区域消息处理能力
  • WS_THICKFRAME支持窗口缩放,WS_MINIMIZEBOX/WS_MAXIMIZEBOX/WS_SYSMENU保留基础窗口控制功能

2. 修正WM_NCHITTEST的坐标转换逻辑

原代码的坐标转换方法错误,需用ScreenToClient将屏幕坐标转为客户端坐标,同时补充窗口边框和标题栏的命中测试:

case WM_NCHITTEST:
{
    POINT screenPt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
    ScreenToClient(hwnd, &screenPt); // 正确转换屏幕点到客户端区域

    // 自定义最大化按钮区域(替换为你的实际按钮坐标)
    RECT maximizeRect = { 800, 10, 850, 40 };
    if (PtInRect(&maximizeRect, screenPt))
    {
        return HTMAXBUTTON; // 通知系统该区域为最大化按钮
    }

    // 窗口边框命中测试,保证窗口可正常缩放
    RECT clientRect;
    GetClientRect(hwnd, &clientRect);
    const int BORDER_WIDTH = 5;
    if (screenPt.x < BORDER_WIDTH) return HTLEFT;
    if (screenPt.x > clientRect.right - BORDER_WIDTH) return HTRIGHT;
    if (screenPt.y < BORDER_WIDTH) return HTTOP;
    if (screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOM;
    
    if (screenPt.x < BORDER_WIDTH && screenPt.y < BORDER_WIDTH) return HTTOPLEFT;
    if (screenPt.x > clientRect.right - BORDER_WIDTH && screenPt.y < BORDER_WIDTH) return HTTOPRIGHT;
    if (screenPt.x < BORDER_WIDTH && screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOMLEFT;
    if (screenPt.x > clientRect.right - BORDER_WIDTH && screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOMRIGHT;

    // 自定义标题栏拖拽区域(替换为你的标题栏范围)
    RECT titleBarRect = {0,0, clientRect.right, 50};
    if (PtInRect(&titleBarRect, screenPt))
    {
        return HTCAPTION; // 允许拖拽窗口
    }
    break;
}

3. 启用DWM非客户端渲染属性

Windows 11的Snap布局依赖DWM支持,窗口创建后需调用DwmSetWindowAttribute开启非客户端渲染:

// 在ShowWindow之前添加
#pragma comment(lib, "dwmapi.lib") // 链接DWM库
DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED;
DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof(policy));

4. 处理WM_NCCALCSIZE消息,合并系统标题栏空间

即使调整了窗口样式,系统仍可能预留标题栏空间,需通过该消息将非客户端区域合并到客户端区域:

case WM_NCCALCSIZE:
{
    if (wParam == TRUE)
    {
        NCCALCSIZE_PARAMS* pParams = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
        pParams->rgrc[0].top = 0; // 移除系统标题栏高度,客户端区域从(0,0)开始
        return 0;
    }
    break;
}

5. 满足Snap布局的系统要求

  • 应用需运行在Windows 11 22H2及以上版本
  • 窗口不能使用WS_EX_TOOLWINDOW扩展样式
  • 必须保留WS_MAXIMIZEBOX窗口样式

完整修正代码示例

#pragma comment(lib, "dwmapi.lib")
#include <stdio.h>
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <windowsx.h>
#include <dwmapi.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    const wchar_t CLASS_NAME[] = L"Custom Titlebar Window Class";

    WNDCLASS wc = { };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,
        CLASS_NAME,
        L"Custom Titlebar Snap Test",
        WS_POPUPWINDOW | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 900, 600,
        NULL, NULL, hInstance, NULL
    );

    if (hwnd == NULL)
    {
        return 0;
    }

    // 启用DWM非客户端渲染
    DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED;
    DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof(policy));

    ShowWindow(hwnd, nCmdShow);

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

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        // 绘制自定义标题栏背景(示例)
        RECT titleBarRect = {0,0, 900, 50};
        FillRect(hdc, &titleBarRect, (HBRUSH)(COLOR_ACTIVECAPTION + 1));
        // 绘制最大化按钮(示例)
        RECT maxBtnRect = {800,10,850,40};
        FillRect(hdc, &maxBtnRect, (HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(hwnd, &ps);
    }
    return 0;

    case WM_NCHITTEST:
    {
        POINT screenPt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
        ScreenToClient(hwnd, &screenPt);

        // 自定义最大化按钮区域
        RECT maximizeRect = { 800, 10, 850, 40 };
        if (PtInRect(&maximizeRect, screenPt))
        {
            return HTMAXBUTTON;
        }

        // 窗口边框命中测试
        RECT clientRect;
        GetClientRect(hwnd, &clientRect);
        const int BORDER_WIDTH = 5;

        if (screenPt.x < BORDER_WIDTH) return HTLEFT;
        if (screenPt.x > clientRect.right - BORDER_WIDTH) return HTRIGHT;
        if (screenPt.y < BORDER_WIDTH) return HTTOP;
        if (screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOM;

        if (screenPt.x < BORDER_WIDTH && screenPt.y < BORDER_WIDTH) return HTTOPLEFT;
        if (screenPt.x > clientRect.right - BORDER_WIDTH && screenPt.y < BORDER_WIDTH) return HTTOPRIGHT;
        if (screenPt.x < BORDER_WIDTH && screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOMLEFT;
        if (screenPt.x > clientRect.right - BORDER_WIDTH && screenPt.y > clientRect.bottom - BORDER_WIDTH) return HTBOTTOMRIGHT;

        // 自定义标题栏拖拽区域
        RECT titleBarRect = {0,0, clientRect.right, 50};
        if (PtInRect(&titleBarRect, screenPt))
        {
            return HTCAPTION;
        }

        break;
    }

    case WM_NCCALCSIZE:
    {
        if (wParam == TRUE)
        {
            NCCALCSIZE_PARAMS* pParams = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
            pParams->rgrc[0].top = 0;
            return 0;
        }
        break;
    }

    case WM_DWMCOMPOSITIONCHANGED:
        // DWM组合状态变化时重新启用非客户端渲染
        DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED;
        DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof(policy));
        return 0;
    }

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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:54:53