前情提要——之前写了一个不规则形状的窗口,而在一个无边框的自定义窗口中,是没有办法拖拽移动的,因为传统的Windows窗口通常只能通过标题栏来移动。因此,就想要实现通过拖动某个特定的区域来移动整个窗口的功能。
技术实现
主要思路是使用 Windows 的钩子机制来捕获鼠标事件,并在特定条件下移动窗口。我创建了一个 MoveHelper 类来管理所有的变量和方法。
1. 钩子设置
void MoveHelper::SetMouseHook() {
if (hMouseHook == NULL) {
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, GetModuleHandle(NULL), 0);
// ... 错误处理
}
}
使用 SetWindowsHookEx函数 设置一个低级鼠标钩子,这允许我们捕获系统范围内的鼠标事件。
2. 鼠标事件处理
LRESULT CALLBACK MoveHelper::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
// ... 代码省略
switch (wParam) {
case WM_LBUTTONDOWN: {
// 检查鼠标是否在可移动区域内
if (IsRectMoveable() && PtInRect(&CursorPosCalculator::movableRect, ptInitial)) {
bRectPressed = true;
GetCursorPos(&ptDragPos);
SetCursor(LoadCursor(NULL, IDC_HAND));
}
return 0;
}
case WM_MOUSEMOVE: {
if (bLeftButtonPressed && bRectPressed && IsRectMoveable()) {
// 计算鼠标移动距离并移动窗口
POINT currentPoint;
GetCursorPos(¤tPoint);
int dx = currentPoint.x - ptDragPos.x;
int dy = currentPoint.y - ptDragPos.y;
RECT rect;
GetWindowRect(hwnd, &rect);
MoveWindow(hwnd, rect.left + dx, rect.top + dy, rect.right - rect.left, rect.bottom - rect.top, TRUE);
ptDragPos = currentPoint;
}
return 0;
}
// ... 其他case
}
// ... 代码省略
}
在这个钩子过程中,我们处理了鼠标按下和移动事件。当用户在指定的可移动区域内按下鼠标左键并拖动时,我们就会相应地移动窗口。
3. 设置可移动区域
void MoveHelper::SetMovableRect(const RECT& rect) {
CursorPosCalculator::SetMovableRect(rect);
}
这个方法允许我们指定窗口的哪个区域是可以用来移动窗口的。
注意
-
性能考虑:使用全局钩子可能会对系统性能产生影响,特别是在处理大量鼠标事件时。
-
清理资源:在不再需要时卸载钩子并释放资源,避免内存泄漏。
macOS
macOS中类似,响应 mouseDragged 方法:
- (void)mouseDragged: (NSEvent *)event {
if (self.isRectPressed) {
NSPoint newLocation = [self.window convertBaseToScreen:[event locationInWindow]];
// ……
NSRect newFrame = self.window.frame;
newFrame.origin.x += deltaX;
newFrame.origin.y += deltaY;
[self.window setFrame:newFrame display:YES animate:NO];
// ……
}
}
总结
通过使用Win32 API和钩子机制,可以实现一个灵活的解决方案,允许窗口的特定区域成为可拖动区域。当然还有很多可拓展的功能,能够创造出既美观又实用的用户界面🎉🎉