【PyQt】重写系统事件之拖动改变窗口大小

:系列文章,前后关联,请结合完整代码参考本系列文章;现已开源在 GitHub PyOc

参考链接

  1. QT 创建一个 可移动、可拉伸的无边框窗体
  2. QT 鼠标跟踪

关于鼠标追踪 (重点) ***

  • 属性 mouseTracking : bool 默认值 为 False 只在鼠标任一按键按键按下时触发鼠标移动事件

  • 方法 void setMouseTracking(bool enable) 设置鼠标追踪属性

  • 方法 bool hasMouseTracking() 获取当前鼠标追踪状态

注意:若想在 QMainWindow 中若要开启鼠标追踪,必须:

  1. 主窗口centralWidget 同时开启鼠标追踪

  2. 并且开启所有覆盖在你想开启鼠标追踪区域的所有 子控件 的鼠标追踪

以上两个条件必须同同时满足,否则鼠标追踪会被子控件遮挡,没有效果
在这里插入图片描述
在这里插入图片描述

核心代码

窗口移动需要使用到鼠标事件,用新旧坐标之差计算偏移量

def _resize(self, event):
    """实现拖动调整窗口大小的函数

    以新旧坐标差计算偏移量,使用 QRect 实例附带位置坐标;
    核心算法做了三重校验,以确保任意情况下窗口都能以正确的方式调整大小:
        一: 横纵坐标与最值校验,确保在最值范围内调整大小;
        二: 横纵坐标与左右区块校验,确保鼠标在窗口边缘时才调整大小;
        三: 横纵坐标与极值偏移量校验,确保在改变坐标的情况下,窗口不会发生漂移
    """
    # 鼠标在窗口中的区域
    area = self._area
    # 鼠标偏移量
    offsetPos = event.globalPos() - self._posLast
    # 鼠标在窗口中的坐标
    winPos = event.pos()

    # 矩形实例,被赋予窗口的几何属性(x, y, width, height)
    # 利用其改变左上角坐标,但右下角坐标不变的特性,实现窗口移动效果
    rect = QRect(self.geometry())

    x = rect.x()
    y = rect.y()
    width = rect.width()
    height = rect.height()

    minWidth = self.minimumWidth()
    minHeight = self.minimumHeight()
    maxWidth = self.maximumWidth()
    maxHeight = self.maximumHeight()

    # 根据不同区域选择不同操作
    if area == 11:
        # 左上
        pos = rect.topLeft()

        if offsetPos.x() < 0 and width < maxWidth or offsetPos.x() > 0 and width > minWidth:
            if offsetPos.x() < 0 and winPos.x() <= 0 or offsetPos.x() > 0 and winPos.x() >= 0:
                if (maxWidth - width) >= -offsetPos.x() and (width - minWidth) >= offsetPos.x():
                    pos.setX(pos.x() + offsetPos.x())

        if offsetPos.y() < 0 and height < maxHeight or offsetPos.y() > 0 and height > minHeight:
            if offsetPos.y() < 0 and winPos.y() <= 0 or offsetPos.y() > 0 and winPos.y() >= 0:
                if (maxHeight - height) >= -offsetPos.y() and (height - minHeight) >= offsetPos.y():
                    pos.setY(pos.y() + offsetPos.y())

        rect.setTopLeft(pos)

    elif area == 13:
        # 右上
        pos = rect.topRight()

        if offsetPos.x() < 0 and width > minWidth or offsetPos.x() > 0 and width < maxWidth:
            if offsetPos.x() < 0 and winPos.x() <= width or offsetPos.x() > 0 and winPos.x() >= width:
                pos.setX(pos.x() + offsetPos.x())

        if offsetPos.y() < 0 and height < maxHeight or offsetPos.y() > 0 and height > minHeight:
            if offsetPos.y() < 0 and winPos.y() <= 0 or offsetPos.y() > 0 and winPos.y() >= 0:
                if (maxHeight - height) >= -offsetPos.y() and (height - minHeight) >= offsetPos.y():
                    pos.setY(pos.y() + offsetPos.y())

        rect.setTopRight(pos)

    elif area == 31:
        # 左下
        pos = rect.bottomLeft()

        if offsetPos.x() < 0 and width < maxWidth or offsetPos.x() > 0 and width > minWidth:
            if offsetPos.x() < 0 and winPos.x() <= 0 or offsetPos.x() > 0 and winPos.x() >= 0:
                if (maxWidth - width) >= -offsetPos.x() and (width - minWidth) >= offsetPos.x():
                    pos.setX(pos.x() + offsetPos.x())

        if offsetPos.y() < 0 and height > minHeight or offsetPos.y() > 0 and height < maxHeight:
            if offsetPos.y() < 0 and winPos.y() <= height or offsetPos.y() > 0 and winPos.y() >= height:
                pos.setY(pos.y() + offsetPos.y())

        rect.setBottomLeft(pos)

    elif area == 33:
        # 右下
        pos = rect.bottomRight()

        if offsetPos.x() < 0 and width > minWidth or offsetPos.x() > 0 and width < maxWidth:
            if offsetPos.x() < 0 and winPos.x() <= width or offsetPos.x() > 0 and winPos.x() >= width:
                pos.setX(pos.x() + offsetPos.x())

        if offsetPos.y() < 0 and height > minHeight or offsetPos.y() > 0 and height < maxHeight:
            if offsetPos.y() < 0 and winPos.y() <= height or offsetPos.y() > 0 and winPos.y() >= height:
                pos.setY(pos.y() + offsetPos.y())

        rect.setBottomRight(pos)

    elif area == 12:
        # 中上
        if offsetPos.y() < 0 and height < maxHeight or offsetPos.y() > 0 and height > minHeight:
            if offsetPos.y() < 0 and winPos.y() <= 0 or offsetPos.y() > 0 and winPos.y() >= 0:
                if (maxHeight - height) >= -offsetPos.y() and (height - minHeight) >= offsetPos.y():
                    rect.setTop(rect.top() + offsetPos.y())

    elif area == 21:
        # 中左
        if offsetPos.x() < 0 and width < maxWidth or offsetPos.x() > 0 and width > minWidth:
            if offsetPos.x() < 0 and winPos.x() <= 0 or offsetPos.x() > 0 and winPos.x() >= 0:
                if (maxWidth - width) >= -offsetPos.x() and (width - minWidth) >= offsetPos.x():
                    rect.setLeft(rect.left() + offsetPos.x())

    elif area == 23:
        # 中右
        if offsetPos.x() < 0 and width > minWidth or offsetPos.x() > 0 and width < maxWidth:
            if offsetPos.x() < 0 and winPos.x() <= width or offsetPos.x() > 0 and winPos.x() >= width:
                rect.setRight(rect.right() + offsetPos.x())

    elif area == 32:
        # 中下
        if offsetPos.y() < 0 and height > minHeight or offsetPos.y() > 0 and height < maxHeight:
            if offsetPos.y() < 0 and winPos.y() <= height or offsetPos.y() > 0 and winPos.y() >= height:
                rect.setBottom(rect.bottom() + offsetPos.y())

    # 设置窗口几何属性(坐标,宽高)
    self.setGeometry(rect)

设置光标图标

    def _change_cursor_icon(self, area):
        """改变光标在窗口边缘时的图片"""

        # 宽度固定时不应改变宽度
        if self.maximumWidth() == self.minimumWidth() and (area == 21 or area == 23):
            return None
        # 高度固定时不应改变高度
        if self.maximumHeight() == self.minimumHeight() and (area == 12 or area == 32):
            return None

        if area == 11 or area == 33:
            self.setCursor(Qt.SizeFDiagCursor)				# 倾斜光标
        elif area == 12 or area == 32:
            self.setCursor(Qt.SizeVerCursor)				# 垂直大小光标
        elif area == 13 or area == 31:
            self.setCursor(Qt.SizeBDiagCursor)				# 反倾斜光标
        elif area == 21 or area == 23:
            self.setCursor(Qt.SizeHorCursor)				# 水平大小光标
        else:
            self.setCursor(Qt.ArrowCursor)					# 默认光标

事件

def mousePressEvent(self, event):
    """重写继承的鼠标按住事件"""

    self._isPressed = True                              # 判断是否按下
    self._press_button = event.button()                 # 按下的鼠标按键
    self._area = self._compute_area(event.pos())        # 计算鼠标所在区域
    self._move_count = 0                                # 鼠标移动计数,用于降低灵敏度
    self._posLast = event.globalPos()                   # 当前坐标

    return QMainWindow.mousePressEvent(self, event)     # 交由原事件函数处理

def mouseReleaseEvent(self, event):
    """重写继承的鼠标释放事件"""

    self._isPressed = False                             # 重置按下状态
    self._press_button = None                           # 清空按下的鼠标按键
    self._area = None                                   # 清空鼠标区域
    self._move_count = 0                                # 清空移动计数
    self.setCursor(Qt.ArrowCursor)                      # 还原光标图标

    return QMainWindow.mouseReleaseEvent(self, event)
        
def mouseMoveEvent(self, event):
    """重写继承的鼠标移动事件,实现窗口移动及拖动改变窗口大小"""

    area = self._compute_area(event.pos())              # 计算鼠标区域

    # 调整窗口大小及移动
    if self._isPressed and self._press_button == Qt.LeftButton:
        if self._area == 22:
            self._move(event)                           # 调用移动窗口的函数
        elif not self.isMaximized():
            self._resize(event)                         # 调用调整窗口大小的函数

        # 更新鼠标全局坐标
        self._posLast = event.globalPos()
        return None
    if not self._isPressed and not self.isMaximized():
        # 调整鼠标图标,按下鼠标后锁定状态
        self._change_cursor_icon(area)

    return QMainWindow.mouseMoveEvent(self, event)

本文为 one-ccs 原创文章,引用必须注明出处!
https://blog.csdn.net/qq_43155814/article/details/104679678

上一篇:【PyQt】实战 Super Spider 之窗口移动

下一篇:【PyQt】实战 Super Spider 之自适应背景图片

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值