Win32自定义标题栏:返回HTREDUCE等后显示系统按钮问题
解决自定义标题栏点击区域时出现系统默认按钮的问题
问题根源
当你在WM_NCHITTEST中返回HTREDUCE、HTZOOM、HTCLOSE这类非客户区命中测试值时,Windows系统会默认认为窗口存在原生标题栏按钮——即便你已经通过WM_NCCALCSIZE移除了系统标题栏,系统仍会自动绘制对应的原生按钮样式,这就是你看到丑陋系统按钮的原因。
解决方案
要彻底避免系统绘制默认按钮,需要调整窗口样式并自行处理按钮的点击逻辑:
移除窗口的
WS_SYSMENU样式
这个样式会让系统保留绘制系统菜单及相关按钮的逻辑,必须移除。修改
WM_NCHITTEST处理逻辑
仅返回HTCAPTION实现标题栏拖拽,不再返回按钮相关的非客户区测试值,将按钮点击交给鼠标消息处理。自行处理按钮点击事件
在WM_LBUTTONDOWN中判断点击位置是否在自定义按钮区域,调用对应API执行最小化、最大化、关闭操作。
修改后的代码示例
创建窗口(移除WS_SYSMENU)
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW & ~WS_SYSMENU, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
WM_NCHITTEST处理(仅保留拖拽逻辑)
case WM_NCHITTEST: { auto x = GET_X_LPARAM(lParam); auto y = GET_Y_LPARAM(lParam); RECT rect; GetWindowRect(hWnd, &rect); if (y - rect.top < 40) { // 仅标记为标题栏区域,支持窗口拖拽 return HTCAPTION; } return DefWindowProc(hWnd, WM_NCHITTEST, wParam, lParam); }
添加WM_LBUTTONDOWN处理按钮点击
case WM_LBUTTONDOWN: { POINT pt; GetCursorPos(&pt); RECT windowRect; GetWindowRect(hWnd, &windowRect); const int btnWidth = 120; const int titleBarHeight = 40; // 计算各按钮的屏幕坐标范围 RECT minBtn = { windowRect.right - 3 * btnWidth, windowRect.top, windowRect.right - 2 * btnWidth, windowRect.top + titleBarHeight }; RECT maxBtn = { windowRect.right - 2 * btnWidth, windowRect.top, windowRect.right - btnWidth, windowRect.top + titleBarHeight }; RECT closeBtn = { windowRect.right - btnWidth, windowRect.top, windowRect.right, windowRect.top + titleBarHeight }; if (PtInRect(&minBtn, pt)) { ShowWindow(hWnd, SW_MINIMIZE); } else if (PtInRect(&maxBtn, pt)) { ShowWindow(hWnd, IsZoomed(hWnd) ? SW_RESTORE : SW_MAXIMIZE); } else if (PtInRect(&closeBtn, pt)) { DestroyWindow(hWnd); } return 0; }
额外优化
如果需要实现按钮的hover、按下等交互效果,还需处理WM_MOUSEMOVE和WM_LBUTTONUP消息,更新按钮状态并调用InvalidateRect触发重绘,让自定义按钮的交互体验更接近原生。
内容的提问来源于stack exchange,提问作者Robin L
相关产品推荐
相关产品推荐

