最近接到一个比较个性化的需求,原来团队基于 Electron 开发的项目由于不支持 XP 系统,想完全移植到基于 CEF 2623 版本的空壳中,以此来兼容 XP 系统。而程序的界面则完全交由前端来处理了,比如标题栏、最小化、最大化等功能按钮。这其中就涉及到一些原生应用的功能比如拖动窗口的功能是需要原生窗口提供的,微软提供拖动窗口的机制如此文章所描述:https://www.cnblogs.com/GnagWang/archive/2010/09/12/1824394.html。为了保证内容不丢失,我特意截图保留一份。

此文章是微软的相关介绍:https://msdn.microsoft.com/en-us/data/923b34d9(v=vs.85),其中包含了 WM_NCHITTEST 消息所有可返回的值。 从上面的资料中可以看到,只要我们将某些固定区域在响应 WM_NCHITTEST 消息的处理函数中返回 HTCAPTION,那么微软就会帮我们实现拖动的效果。除了这些,需求中还有更变态的想法,那就是窗口左侧 300 像素要求可拖动高度是 20 像素,右侧可拖动高度是 50 像素,这也能实现吗?当然,其实你只需要根据左侧和右侧两个分割点,生成两个矩形区域,再响应 WM_NCHITTEST 消息时只要鼠标在这两个矩形范围内,那么就返回 HTCAPTION,思路有了,如何实现呢?看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
LRESULT ChildForm::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (persent_ > 0)
{
POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
::ScreenToClient(GetHWND(), &pt);

UiRect rcClient;
::GetClientRect(GetHWND(), &rcClient);

rcClient.Deflate(m_shadow.GetShadowCorner());

// 左侧可拖动区域位置
UiRect left_rect;
left_rect.left = rcClient.left;
left_rect.top = rcClient.top;
left_rect.right = rcClient.GetWidth() * persent_;
left_rect.bottom = rcClient.top + left_title_height_;

// 右侧可拖动矩形位置
UiRect right_rect;
right_rect.left = rcClient.left + rcClient.GetWidth() * persent_;
right_rect.right = rcClient.right;
right_rect.top = rcClient.top;
right_rect.bottom = rcClient.top + right_title_height_;

DpiManager::GetInstance()->ScaleRect(left_rect);
DpiManager::GetInstance()->ScaleRect(right_rect);

if (left_rect.IsPointIn(pt) right_rect.IsPointIn(pt))
{
return HTCAPTION;
}
}

return __super::OnNcHitTest(uMsg, wParam, lParam, bHandled);
}

代码中重写了父类的 OnNcHitTest 方法(处理 WM_NCHITTEST 消息),根据窗口切分的百分比 persent 值分割窗口左右两侧,生成左侧和右侧两个矩形的 rect。并调用了 IsPointIn 方法(实际是 Win32 API ::PtInRect)判断当前鼠标所在的坐标是不是在两个矩形范围内,如果是则返回该区域是 HTCAPTION,以此让 Windows 帮我们处理拖动请求。另外大家也可以看到,如果系统开启了缩放功能,比如设置自动放大 125%,那么我们是需要根据缩放比例来调整坐标体系的,这点就不在此文章中做多介绍。以上方法即可实现控制不同区域让窗口可以拖动的效果,如果有其他需求欢迎大家讨论。