PyQt窗口FramelessWindowHint模式下自定义窗口缩放与移动功能(by 浅若清风cyf)

背景

众所周知,PyQt的窗口支持隐藏系统默认的窗口,实现自定义窗口的样式。
但是,启用这种模式的窗口随之而来的问题是:无法使用系统默认的窗口缩放,窗口移动等功能。因此,今天博主带来自己编写的一个自定义窗口类,需要使用的小伙伴可以直接下载此代码,将您自己的窗口类继承该类即可拥有以下功能。

  • 附:隐藏默认框架代码:
self.setWindowFlags(Qt.FramelessWindowHint)

功能

  • 窗口向右缩放
  • 窗口向下缩放
  • 窗口向右下缩放
  • 鼠标跟随缩放区域切换鼠标样式
  • 双击标题栏实现窗口缩放与还原
  • 点击标题栏实现窗口移动
  • 最小化、最大化按钮功能(需调用预置的函数,传入自己的按钮对象)
  • 禁用窗口缩放
  • 禁用双击标题栏缩放

部分功能演示

请添加图片描述

原理

  • 坐标状态切换:判断坐标所处位置,切换鼠标样式(注:该功能单独用一个定时器定时检测鼠标状态实现的。若直接放在mouseMoveEvent中会出现切换不及时问题!!!)
  • 其他功能通过重写鼠标点击、双击事件实现
  • 实现以上功能需要自定义几个状态变量来协调各种状态变化

源代码

1、定制窗口基类

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtCore import Qt

'''
定制窗口基类
@Author: 浅若清风cyf
@Date: 2023/06/16
@Description: 支持窗口缩放,标题栏双击最大最小化,解决了依赖mouseMoveEvent识别鼠标位置导致鼠标样式更新不及时的问题
'''


class MainWinCustom(QMainWindow):
    def __init__(self, titleHeight: int = 20):
        super(MainWinCustom, self).__init__()

        # 鼠标拖拽缩放窗口的识别区域宽度
        self._padding = 20
        # 设置标题栏的高度,作为双击标题栏的识别区域范围
        self.titleHeight = titleHeight
        # 隐藏系统默认框架
        self.setWindowFlags(Qt.FramelessWindowHint)
        # 一些鼠标状态
        self.is_mousePressed = False
        self.is_resizing = False
        self.resize_direction = 'right'
        # 一些功能启用状态
        self.support_resize = True
        self.support_double_max = True

        self.timer_cursorUpdater = QTimer(self)

    def set_mouse_tracking(self):
        self.ui.frame.setMouseTracking(True)
        self.ui.centralwidget.setMouseTracking(True)
        self.setMouseTracking(True)  # / 设置widget鼠标跟踪

    def disable_window_resize(self):
        self.support_resize = False
        self.stop_cursorUpdater()

    def disable_double_max(self):
        self.support_double_max = False

    def set_titleHeight(self, height: int):
        self.titleHeight = height

    def set_frame_style(self, frame: QFrame):
        frame.setFrameStyle(QFrame.Panel)

    def set_comboBoxView(self):
        comboBoxes = self.findChildren(QComboBox)
        for comboBox in comboBoxes:
            comboBox.setView(QListView())

    def set_max_button(self, btn: QToolButton or QPushButton):
        btn.clicked.connect(self.change_window_size)

    def set_min_button(self, btn: QToolButton or QPushButton):
        btn.clicked.connect(self.showMinimized)

    def set_close_button(self, btn: QToolButton or QPushButton):
        btn.clicked.connect(self.close)

    '''
        以下重写窗口拖拽事件
    '''

    def resizeEvent(self, QResizeEvent):
        # 获取有效识别区域
        self.right_l = self.width() - self._padding
        self.right_r = self.width() + 1
        self.bottom_u = self.height() - self._padding
        self.bottom_d = self.height() + 1
        # # 识别区域
        # print('x识别区间:', self.right_l, ',', self.right_r)
        # print('y识别区间:', self.bottom_u, ',', self.bottom_d)

    def get_mouse_pos_of_window(self) -> QPoint:
        global_pos = QCursor.pos()
        window_pos = self.mapFromGlobal(global_pos)
        return window_pos

    def get_mouse_global_pos(self) -> QPoint:
        return QCursor.pos()

    def check_pos_status(self):
        '''
        获取鼠标相对于窗口的坐标,根据鼠标所处的位置返回鼠标的状态
        '''
        pos: QPoint = self.get_mouse_pos_of_window()
        if pos.x() >= self.right_l and pos.x() < self.right_r:
            if pos.y() >= 0:
                if pos.y() <= self.bottom_u:
                    return pos, "right"  # 右边界
                elif pos.y() < self.bottom_d:
                    return pos, "corner"  # 右下角
        if pos.y() >= self.bottom_u and pos.y() < self.bottom_d:
            if pos.x() >= 0:
                if pos.x() <= self.right_l:
                    return pos, 'bottom'  # 下边界
        if pos.x() >= 0 and pos.x() <= self.right_l and pos.y() >= 0 and pos.y() <= self.titleHeight:
            return pos, 'title'
        return pos, False

    def start_cursorUpdater(self):
        '''
        启动计时器更新鼠标样式
        '''
        if self.timer_cursorUpdater:
            self.timer_cursorUpdater.timeout.connect(self.update_cursor)
            self.timer_cursorUpdater.start(100)  # 每隔1秒触发一次鼠标移动事件

    def stop_cursorUpdater(self):
        '''
        停止计时器更新鼠标样式
        '''
        if self.timer_cursorUpdater:
            self.timer_cursorUpdater.stop()

    def update_cursor(self):
        '''
        根据鼠标坐标获取状态,设定鼠标样式
        '''
        pos, status = self.check_pos_status()
        if status is False:
            self.setCursor(Qt.ArrowCursor)
        elif status == 'right':
            self.setCursor(Qt.SizeHorCursor)
        elif status == 'bottom':
            self.setCursor(Qt.SizeVerCursor)
        elif status == 'corner':
            self.setCursor(Qt.SizeFDiagCursor)
        else:
            self.setCursor(Qt.ArrowCursor)

    def change_window_size(self):
        if not self.isMaximized():
            self.showMaximized()
            self.stop_cursorUpdater()
        elif self.isMaximized():
            self.showNormal()
            self.start_cursorUpdater()
        return self.isMaximized()

    def mousePressEvent(self, event: QMouseEvent):
        self.is_mousePressed = True
        self.mouse_pos = self.get_mouse_global_pos()

    def mouseReleaseEvent(self, event: QMouseEvent):
        self.is_mousePressed = False
        self.is_resizing = False

    def mouseDoubleClickEvent(self, *args, **kwargs):
        '''
        检测标题栏的双击状态,切换最大化与正常窗口
        '''
        if self.check_pos_status()[1] == 'title' and self.support_double_max:
            self.change_window_size()

    def mouseMoveEvent(self, event: QMouseEvent):
        if self.is_resizing:
            pos = self.get_mouse_pos_of_window()
            if self.resize_direction == 'right' and self.support_resize:
                self.resize(pos.x(), self.height())
            elif self.resize_direction == 'down' and self.support_resize:
                self.resize(self.width(), pos.y())
            elif self.resize_direction == 'right-down' and self.support_resize:
                self.resize(pos.x(), pos.y())
            elif self.resize_direction == 'move':
                current_pos = self.get_mouse_global_pos()
                self.move(self.pos() + current_pos - self.mouse_pos)
                self.mouse_pos = current_pos
        else:
            if self.is_mousePressed:
                pos, status = self.check_pos_status()
                if status is False:
                    self.is_resizing = False
                if status == 'right':
                    # self.resize(pos.x(), self.height())
                    self.is_resizing = True
                    self.resize_direction = 'right'
                elif status == 'bottom':
                    self.is_resizing = True
                    self.resize_direction = 'down'
                elif status == 'corner':
                    self.is_resizing = True
                    self.resize_direction = 'right-down'
                elif status == 'title':
                    self.is_resizing = True
                    self.resize_direction = 'move'

    def hideEvent(self, *args, **kwargs):
        self.stop_cursorUpdater()

    def showEvent(self, *args, **kwargs):
        if self.support_resize:
            self.start_cursorUpdater()

2、使用示例

class MyWin(MainWinCustom):
    def __init__(self):
        super(MyWin2, self).__init__()

        self.resize(200, 300)

        # 创建一个 QWidget 作为中心区域的容器
        central_widget = QWidget(self)

        # 在中心容器中添加其他控件
        label_title = QLabel("这是标题栏", central_widget)
        label_title.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        label_title.setFixedHeight(40)
        label_title.setStyleSheet("""
        QLabel{
        background: yellow;
        }""")
        layout = QVBoxLayout(central_widget)
        layout.addWidget(label_title)
        label = QLabel('Power by 浅若清风cyf\n\t2023/06/16\n')
        label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        layout.addWidget(label)
        layout.setContentsMargins(0, 0, 0, 0)

        # 将中心容器设置为 QMainWindow 的中心部件
        self.setCentralWidget(central_widget)
		# 设置label_title的高度为有效识别区域
        self.set_titleHeight(label_title.height())


if __name__ == '__main__':
    QtCore.QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QtGui.QGuiApplication.setAttribute(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) 
    app = QApplication(sys.argv)
    win = MyWin()
    win.show()
    sys.exit(app.exec_())
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅若清风cyf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值