Win32 API自定义窗口拖拽区域

1,091 阅读2分钟

前情提要——之前写了一个不规则形状的窗口,而在一个无边框的自定义窗口中,是没有办法拖拽移动的,因为传统的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(&currentPoint);
                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和钩子机制,可以实现一个灵活的解决方案,允许窗口的特定区域成为可拖动区域。当然还有很多可拓展的功能,能够创造出既美观又实用的用户界面🎉🎉