qt自定义无边框、可拉伸、可拖拽移动、阴影窗体

无边框

实现qt无边框窗体,只需要设定窗口属性:

self.setWindowFlag(QtCore.Qt.FramelessWindowHint)

去除系统标题栏的窗口只有主体容器,没有边框阴影。没有突出层次感,而且没有明显边界,如下图。

qt自定义无边框、可拉伸、可拖拽移动、阴影窗体_第1张图片

边框阴影

给窗体添加阴影采用的方案是使用嵌套容器显示阴影。
即使用最顶层的QDialog作为阴影显示的容器,在窗体中新增子容器作为窗体,用于装载页面内容。
qt自定义无边框、可拉伸、可拖拽移动、阴影窗体_第2张图片

红色的是QDialog 用于显示白色widget的阴影,只需要设置其背景为透明,同时给widget添加阴影那效果如下:

qt自定义无边框、可拉伸、可拖拽移动、阴影窗体_第3张图片
完整代码如下:

# coding=utf-8
"""
    create by pymu
    on 2020/12/10
    at 17:17
"""
import sys

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QDialog


class FrameLessWindowHint(QDialog):
    # 页面上的主要容器,控件应该放在这个里面
    body_widget: QtWidgets.QWidget = None

    def __init__(self, flag=None):
        """自定义窗口"""
        super().__init__(flag)
        self.procedure()

    def procedure(self):
        """
        初始化流程
        """
        self.place()
        self.configure()
        self.set_signal()

    def set_signal(self):
        pass

    def configure(self):
        """
        设置主窗体背景透明;隐藏边框;
        """
        self.resize(800, 600)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.setStyleSheet('''QDialog{  border-radius:7px;background-color:red;}''')
        self.set_default_window_shadow()

    def set_default_window_shadow(self):
        """设置默认阴影"""
        effect_shadow = QtWidgets.QGraphicsDropShadowEffect(self)
        # 偏移
        effect_shadow.setOffset(0, 0)
        # 阴影半径
        effect_shadow.setBlurRadius(10)
        # 阴影颜色
        effect_shadow.setColor(QtCore.Qt.red)
        self.set_window_shadow(effect_shadow)

    def set_window_shadow(self, shadow: QtWidgets.QGraphicsDropShadowEffect):
        """设置窗口的阴影"""
        self.body_widget.setGraphicsEffect(shadow)

    def place(self):
        """
        创建一个无边框的窗体,附带界面阴影窗口拉伸
        """
        body_layout = QtWidgets.QHBoxLayout(self)
        self.body_widget = QtWidgets.QWidget()
        self.body_widget.setStyleSheet('''QWidget{border-radius:7px;background-color:rgb(255,255,255);}''')
        body_layout.addWidget(self.body_widget)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = FrameLessWindowHint()
    MainWindow.show()
    sys.exit(app.exec_())

拖拽移动

设置窗体拖拽移动,需要监听鼠标事件,一般是三个事件:鼠标点击,鼠标移动,鼠标点击释放。
当鼠标左键按下时触发窗口移动开关为打开状态,释放时开关为关闭状态,那如此在鼠标移动的回调函数中判断当前是鼠标左键且窗口移动开关为打开状态,就调用 move函数移动状态,而达到拖拽移动的目的。所以需要三个初始化三个全局参数:bar是可以点击的位置,此处我使用的是主体空间 body_widget;move_drag_position 移动坐标是绝对坐标与窗体的相对位置,如果不使用窗体的左上角会移动到鼠标左键的位置;_move_drag 扳机主要是校验鼠标是不是在拖拽过程中,如果不是那就不用移动。

# 顶部标题栏
bar: QtWidgets.QWidget = None

 # 移动坐标
move_drag_position: QPoint = None

 # 扳机默认值
 _move_drag: bool = False
 
 # 指定可以点击的控件,这里使用主体容器
self.bar = self.body_widget

重构鼠标的三个事件监听:

    def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
        """重构鼠标点击事件"""
        if self.bar and event.button() == Qt.LeftButton and event.y() < self.bar.height():
            self._move_drag = True
            self.move_drag_position = event.globalPos() - self.pos()
            event.accept()
        return super(FrameLessWindowHint, self).mousePressEvent(event)

    def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标移动事件"""
        if self.body_widget:
            pass
        if Qt.LeftButton and self._move_drag:
            self.move(event.globalPos() - self.move_drag_position)
            event.accept()
        return super(FrameLessWindowHint, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标释放事件"""
        self._move_drag = False
        return super(FrameLessWindowHint, self).mouseReleaseEvent(event)

窗口拉伸

与窗口拖拽事件一致,通过监听鼠标事件改变窗体属性 resize 设置窗体大小,在调用setGeometry设置窗体显示位置与大小时会出现窗口闪烁现象,未能解决,所以添加是否在边界显示拉伸功能(也可以拉伸,就是闪烁,其实也没什么我看了微信跟酷狗音乐在除了右、下、右下几个方向外也同样存在这个问题,本着强迫原则,添加边界拉伸开关,默认是右、下、右下是可以拉伸的)。
对程序进行聚合,做无边框,阴影拉伸窗体,后续的项目会依托这个模板进行开发,所以最后的样子是:
如果需要单独运行的文件,请移步至:末尾

# coding=utf-8
"""
    create by pymu
    on 2020/12/10
    at 17:17
"""
import sys
from typing import Tuple

from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QDialog

from view.base_view import BaseView


class FrameLessWindowHint(QDialog, BaseView):
    # 页面上的主要容器,控件应该放在这个里面
    body_widget: QtWidgets.QWidget = None
    # 顶部标题栏
    bar: QtWidgets.QWidget = None
    # 移动坐标
    move_drag_position: QPoint = None
    # 窗口拉伸边界
    border: int = 5

    class EventFlags:
        """扳机状态,用于判定鼠标事件是否触发"""
        event_flag_bar_move = False
        event_flag_border_left = False
        event_flag_border_right = False
        event_flag_border_top = False
        event_flag_border_bottom = False
        event_flag_border_top_left = False
        event_flag_border_top_right = False
        event_flag_border_bottom_left = False
        event_flag_border_bottom_right = False

        # 不得已以为拉伸闪烁问题
        # 只能设定固定方向的拉伸能够使用
        # 当然全部打开也是可以的,只是存在闪烁问题
        # PC端的应用大部分存在这个问题,所以用也可以
        event_switch_border_left = False
        event_switch_border_right = True
        event_switch_border_top = False
        event_switch_border_bottom = True
        event_switch_border_top_left = False
        event_switch_border_top_right = False
        event_switch_border_bottom_left = False
        event_switch_border_bottom_right = True

    def __init__(self, flag=None):
        """自定义窗口"""
        super().__init__(flag)
        self.procedure()

    def procedure(self):
        """
        初始化流程
        """
        self.place()
        self.configure()
        self.set_signal()

    def set_signal(self):
        pass

    def configure(self):
        """
        设置主窗体背景透明;隐藏边框;
        """
        self.resize(1047, 680)
        self.setWindowTitle("应用名称")
        self.set_style("common.css")
        self.setMouseTracking(True)
        self.body_widget.setMouseTracking(True)
        self.setWindowIcon(self.resource.qt_icon_project_ico)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.set_default_window_shadow()
        self.bar = self.body_widget

    def set_default_window_shadow(self):
        """设置默认阴影"""
        effect_shadow = QtWidgets.QGraphicsDropShadowEffect(self)
        # 偏移
        effect_shadow.setOffset(0, 0)
        # 阴影半径
        effect_shadow.setBlurRadius(10)
        # 阴影颜色
        effect_shadow.setColor(QtCore.Qt.red)
        self.set_window_shadow(effect_shadow)

    def set_window_shadow(self, shadow: QtWidgets.QGraphicsDropShadowEffect):
        """设置窗口的阴影"""
        self.body_widget.setGraphicsEffect(shadow)

    def place(self):
        """
        创建一个无边框的窗体,附带界面阴影窗口拉伸
        """
        body_layout = QtWidgets.QHBoxLayout(self)
        self.body_widget = QtWidgets.QWidget()
        body_layout.addWidget(self.body_widget)

    def event_flag(self, event: QtGui.QMouseEvent) -> Tuple[bool, bool, bool, bool]:
        """判断鼠标是否移动到边界"""
        top = self.border < event.pos().y() < self.border + 10
        bottom = self.border + self.body_widget.height() < event.pos().y() < self.height()
        left = self.border < event.pos().x() < self.border + 10
        right = self.border + self.body_widget.width() < event.pos().x() < self.width()
        return top, bottom, left, right

    def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
        """重构鼠标点击事件"""
        super(FrameLessWindowHint, self).mousePressEvent(event)
        if not self.body_widget:
            return super(FrameLessWindowHint, self).mousePressEvent(event)
        top, bottom, left, right = self.event_flag(event)
        # 左键事件
        if event.button() == Qt.LeftButton:
            self.move_drag_position = event.globalPos() - self.pos()
            if top and left and self.EventFlags.event_switch_border_top_left:
                self.EventFlags.event_flag_border_top_left = True
            elif top and right and self.EventFlags.event_switch_border_top_right:
                self.EventFlags.event_flag_border_top_right = True
            elif bottom and left and self.EventFlags.event_switch_border_bottom_left:
                self.EventFlags.event_flag_border_bottom_left = True
            elif bottom and right and self.EventFlags.event_switch_border_bottom_right:
                self.EventFlags.event_flag_border_bottom_right = True
            elif top and self.EventFlags.event_switch_border_top:
                self.EventFlags.event_flag_border_top = True
            elif bottom and self.EventFlags.event_switch_border_bottom:
                self.EventFlags.event_flag_border_bottom = True
            elif left and self.EventFlags.event_switch_border_left:
                self.EventFlags.event_flag_border_left = True
            elif right and self.EventFlags.event_switch_border_right:
                self.EventFlags.event_flag_border_right = True
            elif self.bar and event.y() < self.bar.height():
                self.EventFlags.event_flag_bar_move = True
                self.move_drag_position = event.globalPos() - self.pos()

    def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标移动事件"""
        super(FrameLessWindowHint, self).mouseMoveEvent(event)
        if self.body_widget:
            top, bottom, left, right = self.event_flag(event)
            if top and left and self.EventFlags.event_switch_border_top_left:
                self.setCursor(Qt.SizeFDiagCursor)
            elif bottom and right and self.EventFlags.event_switch_border_bottom_right:
                self.setCursor(Qt.SizeFDiagCursor)
            elif top and right and self.EventFlags.event_switch_border_top_right:
                self.setCursor(Qt.SizeBDiagCursor)
            elif bottom and left and self.EventFlags.event_switch_border_bottom_left:
                self.setCursor(Qt.SizeBDiagCursor)
            elif top and self.EventFlags.event_switch_border_top:
                self.setCursor(Qt.SizeVerCursor)
            elif bottom and self.EventFlags.event_switch_border_bottom:
                self.setCursor(Qt.SizeVerCursor)
            elif left and self.EventFlags.event_switch_border_left:
                self.setCursor(Qt.SizeHorCursor)
            elif right and self.EventFlags.event_switch_border_right:
                self.setCursor(Qt.SizeHorCursor)
            elif Qt.LeftButton and self.EventFlags.event_flag_bar_move:
                self.move(event.globalPos() - self.move_drag_position)
            else:
                self.setCursor(Qt.ArrowCursor)

            # 窗口拉伸
            if self.EventFlags.event_flag_border_top_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y() + event.pos().y(),
                                 self.width() - event.pos().x(), self.height() - event.pos().y())

            elif self.EventFlags.event_flag_border_bottom_right:
                self.resize(event.pos().x(), event.pos().y())

            elif self.EventFlags.event_flag_border_bottom_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y(),
                                 self.width() - event.pos().x(), event.pos().y())

            elif self.EventFlags.event_flag_border_top_right:
                self.setGeometry(self.geometry().x(), self.geometry().y() + event.pos().y(),
                                 event.pos().x(), self.height() - event.pos().y())

            elif self.EventFlags.event_flag_border_right:
                self.resize(event.pos().x(), self.height())

            elif self.EventFlags.event_flag_border_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y(),
                                 self.width() - event.pos().x(), self.height())

            elif self.EventFlags.event_flag_border_bottom:
                self.resize(self.width(), event.pos().y())

            elif self.EventFlags.event_flag_border_top:
                self.setGeometry(self.geometry().x(), self.geometry().y() + event.pos().y(),
                                 self.width(), self.height() - event.pos().y())

    def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标释放事件"""
        super(FrameLessWindowHint, self).mouseReleaseEvent(event)
        self.EventFlags.event_flag_bar_move = False
        self.EventFlags.event_flag_border_left = False
        self.EventFlags.event_flag_border_right = False
        self.EventFlags.event_flag_border_top = False
        self.EventFlags.event_flag_border_bottom = False
        self.EventFlags.event_flag_border_top_left = False
        self.EventFlags.event_flag_border_top_right = False
        self.EventFlags.event_flag_border_bottom_left = False
        self.EventFlags.event_flag_border_bottom_right = False


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = FrameLessWindowHint()
    MainWindow.show()
    sys.exit(app.exec_())

BaseView 父类只是一个约束类

# coding=utf-8
"""
    create by pymu on 2020/10/23
    file: base_view.py
    页面及frame的约束类
"""
import abc

from PyQt5.QtWidgets import QWidget

from common.loader.resource import ResourceLoader


class BaseView(QWidget):
    """
    类包含一个静态资源文件管理器
    """
    # 静态资源管理器
    resource: ResourceLoader = ResourceLoader()

    @abc.abstractmethod
    def set_signal(self):
        """信号设置"""
        ...

    @abc.abstractmethod
    def configure(self):
        """属性配置"""
        ...

    def procedure(self):
        """
        初始化流程, 比如setUi、place、configure、set_signal等
        :return:
        """
        ...

    def place(self):
        """
        页面微布局,并不是所有的页面都是由Qt designer 设计而来的
        还有部分组件需要加载到页面,约定在这里编写,方便管理哦
        :return:
        """
        ...

    def set_style(self, *files: str, use_old_style: bool = False):
        """
        设置样式,可以传入多个文件名,后面声明的样式会覆盖前面声明的样式
        :param use_old_style: 是否使用原来控件上的样式?
        :param files:  样式列表, 相对qss的路径, 如qss/index.css -> index.css
        :return:
        """
        old_style = ""
        if use_old_style:
            old_style = self.styleSheet() or ""
        self.setStyleSheet(old_style + self.resource.style_from(*files))

原生的程序,可单独运行:

# coding=utf-8
"""
    create by pymu
    on 2020/12/10
    at 17:17
"""
import sys
from typing import Tuple

from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QDialog



class FrameLessWindowHint(QDialog):
    # 页面上的主要容器,控件应该放在这个里面
    body_widget: QtWidgets.QWidget = None
    # 顶部标题栏
    bar: QtWidgets.QWidget = None
    # 移动坐标
    move_drag_position: QPoint = None
    # 窗口拉伸边界
    border: int = 5

    class EventFlags:
        """扳机状态,用于判定鼠标事件是否触发"""
        event_flag_bar_move = False
        event_flag_border_left = False
        event_flag_border_right = False
        event_flag_border_top = False
        event_flag_border_bottom = False
        event_flag_border_top_left = False
        event_flag_border_top_right = False
        event_flag_border_bottom_left = False
        event_flag_border_bottom_right = False

        # 不得已以为拉伸闪烁问题
        # 只能设定固定方向的拉伸能够使用
        # 当然全部打开也是可以的,只是存在闪烁问题
        # PC端的应用大部分存在这个问题,所以用也可以
        event_switch_border_left = False
        event_switch_border_right = True
        event_switch_border_top = False
        event_switch_border_bottom = True
        event_switch_border_top_left = False
        event_switch_border_top_right = False
        event_switch_border_bottom_left = False
        event_switch_border_bottom_right = True

    def __init__(self, flag=None):
        """自定义窗口"""
        super().__init__(flag)
        self.procedure()

    def procedure(self):
        """
        初始化流程
        """
        self.place()
        self.configure()
        self.set_signal()

    def set_signal(self):
        pass

    def configure(self):
        """
        设置主窗体背景透明;隐藏边框;
        """
        self.resize(1047, 680)
        self.setWindowTitle("应用名称")
        self.setStyleSheet("QWidget{border-radius:7px;background-color:rgb(255,255,255);}")
        self.setMouseTracking(True)
        self.body_widget.setMouseTracking(True)
        # self.setWindowIcon(self.resource.qt_icon_project_ico)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.set_default_window_shadow()
        self.bar = self.body_widget

    def set_default_window_shadow(self):
        """设置默认阴影"""
        effect_shadow = QtWidgets.QGraphicsDropShadowEffect(self)
        # 偏移
        effect_shadow.setOffset(0, 0)
        # 阴影半径
        effect_shadow.setBlurRadius(10)
        # 阴影颜色
        effect_shadow.setColor(QtCore.Qt.red)
        self.set_window_shadow(effect_shadow)

    def set_window_shadow(self, shadow: QtWidgets.QGraphicsDropShadowEffect):
        """设置窗口的阴影"""
        self.body_widget.setGraphicsEffect(shadow)

    def place(self):
        """
        创建一个无边框的窗体,附带界面阴影窗口拉伸
        """
        body_layout = QtWidgets.QHBoxLayout(self)
        self.body_widget = QtWidgets.QWidget()
        body_layout.addWidget(self.body_widget)

    def event_flag(self, event: QtGui.QMouseEvent) -> Tuple[bool, bool, bool, bool]:
        """判断鼠标是否移动到边界"""
        top = self.border < event.pos().y() < self.border + 10
        bottom = self.border + self.body_widget.height() < event.pos().y() < self.height()
        left = self.border < event.pos().x() < self.border + 10
        right = self.border + self.body_widget.width() < event.pos().x() < self.width()
        return top, bottom, left, right

    def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
        """重构鼠标点击事件"""
        super(FrameLessWindowHint, self).mousePressEvent(event)
        if not self.body_widget:
            return super(FrameLessWindowHint, self).mousePressEvent(event)
        top, bottom, left, right = self.event_flag(event)
        # 左键事件
        if event.button() == Qt.LeftButton:
            self.move_drag_position = event.globalPos() - self.pos()
            if top and left and self.EventFlags.event_switch_border_top_left:
                self.EventFlags.event_flag_border_top_left = True
            elif top and right and self.EventFlags.event_switch_border_top_right:
                self.EventFlags.event_flag_border_top_right = True
            elif bottom and left and self.EventFlags.event_switch_border_bottom_left:
                self.EventFlags.event_flag_border_bottom_left = True
            elif bottom and right and self.EventFlags.event_switch_border_bottom_right:
                self.EventFlags.event_flag_border_bottom_right = True
            elif top and self.EventFlags.event_switch_border_top:
                self.EventFlags.event_flag_border_top = True
            elif bottom and self.EventFlags.event_switch_border_bottom:
                self.EventFlags.event_flag_border_bottom = True
            elif left and self.EventFlags.event_switch_border_left:
                self.EventFlags.event_flag_border_left = True
            elif right and self.EventFlags.event_switch_border_right:
                self.EventFlags.event_flag_border_right = True
            elif self.bar and event.y() < self.bar.height():
                self.EventFlags.event_flag_bar_move = True
                self.move_drag_position = event.globalPos() - self.pos()

    def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标移动事件"""
        super(FrameLessWindowHint, self).mouseMoveEvent(event)
        if self.body_widget:
            top, bottom, left, right = self.event_flag(event)
            if top and left and self.EventFlags.event_switch_border_top_left:
                self.setCursor(Qt.SizeFDiagCursor)
            elif bottom and right and self.EventFlags.event_switch_border_bottom_right:
                self.setCursor(Qt.SizeFDiagCursor)
            elif top and right and self.EventFlags.event_switch_border_top_right:
                self.setCursor(Qt.SizeBDiagCursor)
            elif bottom and left and self.EventFlags.event_switch_border_bottom_left:
                self.setCursor(Qt.SizeBDiagCursor)
            elif top and self.EventFlags.event_switch_border_top:
                self.setCursor(Qt.SizeVerCursor)
            elif bottom and self.EventFlags.event_switch_border_bottom:
                self.setCursor(Qt.SizeVerCursor)
            elif left and self.EventFlags.event_switch_border_left:
                self.setCursor(Qt.SizeHorCursor)
            elif right and self.EventFlags.event_switch_border_right:
                self.setCursor(Qt.SizeHorCursor)
            elif Qt.LeftButton and self.EventFlags.event_flag_bar_move:
                self.move(event.globalPos() - self.move_drag_position)
            else:
                self.setCursor(Qt.ArrowCursor)

            # 窗口拉伸
            if self.EventFlags.event_flag_border_top_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y() + event.pos().y(),
                                 self.width() - event.pos().x(), self.height() - event.pos().y())

            elif self.EventFlags.event_flag_border_bottom_right:
                self.resize(event.pos().x(), event.pos().y())

            elif self.EventFlags.event_flag_border_bottom_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y(),
                                 self.width() - event.pos().x(), event.pos().y())

            elif self.EventFlags.event_flag_border_top_right:
                self.setGeometry(self.geometry().x(), self.geometry().y() + event.pos().y(),
                                 event.pos().x(), self.height() - event.pos().y())

            elif self.EventFlags.event_flag_border_right:
                self.resize(event.pos().x(), self.height())

            elif self.EventFlags.event_flag_border_left:
                self.setGeometry(self.geometry().x() + event.pos().x(), self.geometry().y(),
                                 self.width() - event.pos().x(), self.height())

            elif self.EventFlags.event_flag_border_bottom:
                self.resize(self.width(), event.pos().y())

            elif self.EventFlags.event_flag_border_top:
                self.setGeometry(self.geometry().x(), self.geometry().y() + event.pos().y(),
                                 self.width(), self.height() - event.pos().y())

    def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标释放事件"""
        super(FrameLessWindowHint, self).mouseReleaseEvent(event)
        self.EventFlags.event_flag_bar_move = False
        self.EventFlags.event_flag_border_left = False
        self.EventFlags.event_flag_border_right = False
        self.EventFlags.event_flag_border_top = False
        self.EventFlags.event_flag_border_bottom = False
        self.EventFlags.event_flag_border_top_left = False
        self.EventFlags.event_flag_border_top_right = False
        self.EventFlags.event_flag_border_bottom_left = False
        self.EventFlags.event_flag_border_bottom_right = False


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = FrameLessWindowHint()
    MainWindow.show()
    sys.exit(app.exec_())

你可能感兴趣的:(Qt,for,python,qt,pyqt5,python)