自定义最大化按钮悬停触发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
相关产品推荐
相关产品推荐

