PyQt-自定义标题栏窗口-ElWindow

在大佬文子阳的基础上二次开发

@原作者:pyside6 控件一:自定义窗口(缩放和标题)_pyside6设置窗口名称-CSDN博客

qt版本:pyside6

python测试版本:3.9.19

效果:

引用png图标

     

   

引用资源文件内的图标

 代码:

import enum
from dataclasses import dataclass

from PySide6 import QtWidgets, QtGui
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QPainter, QPaintEvent, QFont
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QSizePolicy, QHBoxLayout, QLayout


class Edge(enum.Flag):
    NoEdge: Qt.Edge = 0  # 不在边缘地带

    TopEdge: Qt.Edge = 1  # 顶部边缘
    LeftEdge: Qt.Edge = 2  # 左部边缘
    RightEdge: Qt.Edge = 3  # 右部边缘
    BottomEdge: Qt.Edge = 4  # 底部边缘

    LeftTopEdge: Qt.Edge = 5  # 左顶部边缘
    RightTopEdge: Qt.Edge = 6  # 右顶部边缘
    LeftBottomEdge: Qt.Edge = 7  # 左底部边缘
    RightBottomEdge: Qt.Edge = 8  # 右底部边缘


@dataclass
class EdgePress:
    leftEdgePress: bool = False
    rightEdgePress: bool = False
    topEdgePress: bool = False
    bottomEdgePress: bool = False
    leftTopEdgePress: bool = False
    rightBottomEdgePress: bool = False
    leftBottomEdgePress: bool = False
    rightTopEdgePress: bool = False
    moveEdgePress: bool = False
    # 点击时的窗口初始位置
    movePosition = None


class ElWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.__title = None
        self.__contentWidget = None
        self.setMouseTracking(True)
        self.resize(100, 100)
        self.setWindowFlags(
            Qt.WindowType.Window
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowSystemMenuHint
            | Qt.WindowType.WindowMinimizeButtonHint
            | Qt.WindowType.WindowMaximizeButtonHint
        )
        # 设置窗体透明
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        # 窗体边缘尺寸(出现缩放标记的范围)
        self.edge_size = 5
        # 窗体圆角X半径
        self.xRadius = 5
        # 窗体圆角Y半径
        self.yRadius = 5
        # 窗体的最小宽度
        self.min_width = 50
        # 窗体的最小高度
        self.min_height = 25
        # 拖放标记
        self.edge_press = EdgePress()
        # 顶部可移动窗口高度
        self.move_event_height = 25
        self.__baseLayout = QVBoxLayout()
        self.__baseLayout.setSizeConstraint(QLayout.SizeConstraint.SetMaximumSize)
        self.__baseLayout.setContentsMargins(0, 0, 0, 0)
        self.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.__baseLayout)

    def setTitle(self, title="title", close_url=None, max_url=None, min_url=None, restore_url=None,
                 title_style=None):
        if self.__title is not None:
            raise ValueError("title 只能设置一次")
        self.__title = title
        _ElTitleBar(self, self.__title, close_url, max_url, min_url, restore_url, title_style)

    def setWidget(self, widget: QWidget):
        if self.__contentWidget is not None:
            raise ValueError("widget 只能设置一次")
        self.__contentWidget = widget
        self.__baseLayout.addWidget(self.__contentWidget)

    def widget(self):
        return self.__contentWidget

    # 设置边框圆角
    def paintEvent(self, event: QPaintEvent) -> None:
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)  # 设置抗锯齿,不然边框会有明显锯齿
        painter.setBrush(Qt.GlobalColor.white)  # 设置窗体颜色
        painter.drawRoundedRect(self.rect(), self.xRadius, self.yRadius)
        super().paintEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.MouseButton.NoButton:
            # 没有鼠标按钮按下,检查鼠标位置并更新窗口光标
            pos = event.globalPosition().toPoint() - self.pos()
            edges = self._get_edges(pos)

            if edges == Edge.LeftEdge or edges == Edge.RightEdge:
                self.setCursor(Qt.CursorShape.SizeHorCursor)
            elif edges == Edge.TopEdge or edges == Edge.BottomEdge:
                self.setCursor(Qt.CursorShape.SizeVerCursor)
            elif edges == Edge.LeftTopEdge or edges == Edge.RightBottomEdge:
                self.setCursor(Qt.CursorShape.SizeFDiagCursor)
            elif edges == Edge.LeftBottomEdge or edges == Edge.RightTopEdge:
                self.setCursor(Qt.CursorShape.SizeBDiagCursor)
            else:
                self.setCursor(Qt.CursorShape.ArrowCursor)
        elif event.buttons() == Qt.MouseButton.LeftButton:
            if self.edge_press.moveEdgePress:
                """移动窗口"""
                self.move(event.globalPosition().toPoint() - self.edge_press.movePosition)  # 更改窗口位置
            elif self._get_edge_press() is not Edge.NoEdge:
                """缩放窗口"""
                self._resize_window(event.globalPosition().toPoint() - self.pos())

    def mousePressEvent(self, event) -> None:

        pos = event.globalPosition().toPoint() - self.pos()
        edges = self._get_edges(pos)
        if edges == Edge.LeftEdge:
            self.edge_press.leftEdgePress = True
        elif edges == Edge.RightEdge:
            self.edge_press.rightEdgePress = True
        elif edges == Edge.TopEdge:
            self.edge_press.topEdgePress = True
        elif edges == Edge.BottomEdge:
            self.edge_press.bottomEdgePress = True
        elif edges == Edge.LeftTopEdge:
            self.edge_press.leftTopEdgePress = True
        elif edges == Edge.RightBottomEdge:
            self.edge_press.rightBottomEdgePress = True
        elif edges == Edge.LeftBottomEdge:
            self.edge_press.leftBottomEdgePress = True
        elif edges == Edge.RightTopEdge:
            self.edge_press.rightTopEdgePress = True
        else:
            # 移动事件
            if self._get_move_edges(pos):
                self.edge_press.moveEdgePress = True
                self.edge_press.movePosition = event.globalPosition().toPoint() - self.pos()
                self.setCursor(Qt.CursorShape.OpenHandCursor)

    def mouseReleaseEvent(self, event) -> None:
        self.edge_press.leftEdgePress = False
        self.edge_press.rightEdgePress = False
        self.edge_press.topEdgePress = False
        self.edge_press.bottomEdgePress = False
        self.edge_press.leftTopEdgePress = False
        self.edge_press.rightBottomEdgePress = False
        self.edge_press.leftBottomEdgePress = False
        self.edge_press.rightTopEdgePress = False
        self.edge_press.moveEdgePress = False
        self.setCursor(Qt.CursorShape.ArrowCursor)

    def _get_move_edges(self, pos):
        """获取移动窗口事件标记"""
        in_move_edge: bool = pos.y() <= self.move_event_height  # 是否在可移动区域内
        not_in_edges: bool = self._get_edges(pos) == Edge.NoEdge  # 是否在非缩放区域内
        return in_move_edge and not_in_edges

    def _get_edges(self, pos):
        """获取边缘类型"""
        edges = Edge.NoEdge

        in_left_edge: bool = pos.x() <= self.edge_size  # 左
        in_right_edge: bool = pos.x() >= (self.width() - self.edge_size)  # 右
        in_top_edge: bool = pos.y() <= self.edge_size  # 上
        in_bottom_edge: bool = pos.y() >= (self.height() - self.edge_size)  # 下

        size = len([i for i in [in_left_edge, in_right_edge, in_top_edge, in_bottom_edge] if i])

        if size == 0:
            return edges
        if size == 1:
            if in_left_edge:
                return Edge.LeftEdge
            if in_right_edge:
                return Edge.RightEdge
            if in_top_edge:
                return Edge.TopEdge
            if in_bottom_edge:
                return Edge.BottomEdge
        if size == 2:
            if in_left_edge and in_top_edge:
                return Edge.LeftTopEdge
            if in_left_edge and in_bottom_edge:
                return Edge.LeftBottomEdge
            if in_right_edge and in_top_edge:
                return Edge.RightTopEdge
            if in_right_edge and in_bottom_edge:
                return Edge.RightBottomEdge

    def _get_edge_press(self):
        """
         边缘按下区域类型
        """
        if self.edge_press.leftEdgePress:
            return Edge.LeftEdge
        elif self.edge_press.rightEdgePress:
            return Edge.RightEdge
        elif self.edge_press.topEdgePress:
            return Edge.TopEdge
        elif self.edge_press.bottomEdgePress:
            return Edge.BottomEdge
        elif self.edge_press.leftTopEdgePress:
            return Edge.LeftTopEdge
        elif self.edge_press.rightBottomEdgePress:
            return Edge.RightBottomEdge
        elif self.edge_press.leftBottomEdgePress:
            return Edge.LeftBottomEdge
        elif self.edge_press.rightTopEdgePress:
            return Edge.RightTopEdge
        else:
            return Edge.NoEdge

    def _resize_window(self, pos):

        """缩放窗口"""
        # 判断按下时的区域是哪一块
        edges = self._get_edge_press()
        # 获取窗口的初始大小和位置
        geo = self.frameGeometry()
        x, y, width, height = geo.x(), geo.y(), geo.width(), geo.height()
        if edges is Edge.LeftEdge:
            width -= pos.x()
            if width <= self.min_width:
                return
            else:
                x += pos.x()
        elif edges is Edge.RightEdge:
            width = pos.x()
            if width <= self.min_width:
                return
        elif edges is Edge.TopEdge:
            height -= pos.y()
            if height <= self.min_height:
                return
            else:
                y += pos.y()
        elif edges is Edge.BottomEdge:
            height = pos.y()
            if height <= self.min_height:
                return
        elif edges is Edge.LeftTopEdge:
            width -= pos.x()
            if width <= self.min_width:
                width = geo.width()
            else:
                x += pos.x()
            height -= pos.y()
            if height <= self.min_height:
                height = geo.height()
            else:
                y += pos.y()
        elif edges is Edge.LeftBottomEdge:
            width -= pos.x()
            if width <= self.min_width:
                width = geo.width()
            else:
                x += pos.x()
            height = pos.y()
            if height <= self.min_height:
                height = geo.height()
        elif edges is Edge.RightTopEdge:
            width = pos.x()
            if width <= self.min_width:
                width = geo.width()
            height -= pos.y()
            if height <= self.min_height:
                height = geo.height()
            else:
                y += pos.y()
        elif edges is Edge.RightBottomEdge:
            width = pos.x()
            if width <= self.min_width:
                width = geo.width()
            height = pos.y()
            if height <= self.min_height:
                height = geo.height()
        self.setGeometry(x, y, width, height)

    def resizeEvent(self, event):
        # 处理窗口大小更改事件
        self.setCursor(Qt.CursorShape.ArrowCursor)


# 自定义标题栏
class _ElTitleBar:
    __title_style = "background-color: rgb(242, 241, 245);" \
                    "color:#333333;border:1px solid #c6c6c6;" \
                    "padding-left:10px;" \
                    "border-top-left-radius:4px;" \
                    "border-top-right-radius:4px;"

    def closeStyle(self):
        return f"border-image:url({self.close_url});background:rgb(242, 241, " \
               "245);border-radius:2px;margin-bottom:3px "

    def maxStyle(self):
        return f"border-image:url({self.max_url});background:rgb(242, 241, " \
               "245);border-radius:2px;margin-bottom:3px "

    def minStyle(self):
        return f"border-image:url({self.min_url});background:rgb(242, 241, " \
               "245);border-radius:2px;margin-bottom:3px "

    def restoreStyle(self):
        return f"border-image:url({self.restore_url});background:rgb(242, 241, " \
               "245);border-radius:2px;margin-bottom:3px "

    def titleStyle(self):
        return self.__title_style

    def __init__(self, window: QWidget, window_title: str = "", close_url=None, max_url=None, min_url=None,
                 restore_url=None, title_style=None):
        if close_url is not None:
            self.close_url = close_url
        if max_url is not None:
            self.max_url = max_url
        if min_url is not None:
            self.min_url = min_url
        if restore_url is not None:
            self.restore_url = restore_url
        if title_style is not None:
            self.__title_style = title_style
        self.window = window
        # 默认标题栏高度 必须设
        self.DEFAULT_TITLE_BAR_HEIGHT = 35
        # 存储父类的双击事件
        self.mouseDoubleClickEvent_parent = self.window.mouseDoubleClickEvent
        # 将本类的双击事件赋值给将父类的双击事件
        self.window.mouseDoubleClickEvent = self.mouseDoubleClickEvent

        # 存储父类的窗口大小改变事件
        self.resizeEvent_parent = self.window.resizeEvent
        # 将本类的窗口大小改变事件赋值给将父类的窗口大小改变事件
        self.window.resizeEvent = self.resizeEvent

        # 设置ui文件里main_layout上边距,以免遮挡标题栏
        self.window.setContentsMargins(0, self.DEFAULT_TITLE_BAR_HEIGHT, 0, 0)

        # 1.设置无边框 和 透明背景 无边框必须设置全,不然会导致点击任务栏不能最小化窗口
        self.window.setWindowFlags(
            Qt.WindowType.Window
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowSystemMenuHint
            | Qt.WindowType.WindowMinimizeButtonHint
            | Qt.WindowType.WindowMaximizeButtonHint
        )
        # self.window.setAttribute(Qt.WA_TranslucentBackground)
        # 2.添加自定义的标题栏到最顶部
        self.title = QLabel(window_title, window)
        self.title.setFont(QFont("STKaiti", 10))
        # 3.设置标题栏样式
        self.setStyle()
        # 4.添加按钮
        # 添加关闭按钮
        self.close_btn = QPushButton("", window)
        self.close_btn.setGeometry(self.window.width() - 33, 10, 20, 20)
        # 添加最大化按钮
        self.max_btn = QPushButton("", window)
        self.max_btn.setGeometry(self.window.width() - 66, 10, 20, 20)
        # 添加最小化按钮
        self.min_btn = QPushButton("", window)
        self.min_btn.setGeometry(self.window.width() - 99, 10, 20, 20)
        # 设置三个按钮的鼠标样式
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.max_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.min_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        # 设置三个按钮的样式
        self.close_btn.setStyleSheet(self.closeStyle())
        self.max_btn.setStyleSheet(self.maxStyle())
        self.min_btn.setStyleSheet(self.minStyle())

        # 5.添加工具栏按钮事件
        # 关闭按钮点击绑定窗口关闭事件
        self.close_btn.clicked.connect(self.window.close)
        # 最大化按钮绑定窗口最大化事件
        self.max_btn.clicked.connect(self.setMaxEvent)
        # 最小化按钮绑定窗口最小化事件
        self.min_btn.clicked.connect(self.window.showMinimized)
        # 6.记录全屏窗口的大小-ps非常有用
        self.window_max_size = None
        # 7.设置标题栏鼠标跟踪 鼠标移入触发,不设置,移入标题栏不触发
        self.title.setMouseTracking(True)

    def setMaxEvent(self, flag=False):
        """
        @description  最大化按钮绑定窗口最大化事件和事件 拿出来是因为拖动标题栏时需要恢复界面大小
        @param flag 是否是拖动标题栏 bool
        @return
        """
        if flag:
            if self.window.isMaximized():
                self.window.showNormal()
                self.max_btn.setStyleSheet(self.maxStyle())
                return self.window_max_size
            return None
        else:
            if self.window.isMaximized():
                self.window.showNormal()
                self.max_btn.setStyleSheet(self.maxStyle())
            else:
                self.window.showMaximized()
                self.max_btn.setStyleSheet(self.restoreStyle())
                # 记录最大化窗口的大小  用于返回最大化时拖动窗口恢复前的大小 这个程序循环帧会取不到恢复前的宽度
                self.window_max_size = QSize(self.window.width(), self.window.height())

    def setStyle(self, style: str = ""):
        """
        @description 设置自定义标题栏样式
        @param
        @return
        """
        # 想要边框 加上border:1px solid #cccccc;
        DEFAULT_STYLE = self.titleStyle()
        self.title.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
        # 设置样式
        self.title.setStyleSheet(DEFAULT_STYLE if not style else DEFAULT_STYLE + style)
        # 设置大小
        self.title.setGeometry(0, 0, self.window.width(), self.DEFAULT_TITLE_BAR_HEIGHT)

    def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
        """
        @description 鼠标双击事件
        @param
        @return
        """
        # 如果双击的是鼠标左键 且在标题栏范围内 则放大缩小窗口
        if a0.button() == Qt.MouseButton.LeftButton and a0.position().y() < self.title.height():
            self.setMaxEvent()
        return self.mouseDoubleClickEvent_parent(a0)

    def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
        """
        @description  窗口缩放事件
        @param
        @return
        """
        # 最大化最小化的时候,需要去改变按钮组位置
        self.close_btn.move(self.window.width() - 33, 10)
        self.max_btn.move(self.window.width() - 66, 10)
        self.min_btn.move(self.window.width() - 99, 10)
        self.title.resize(self.window.width(), self.DEFAULT_TITLE_BAR_HEIGHT)
        return self.resizeEvent_parent(a0)

使用:

class MainWindow(ElWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(450, 200)
        self.setTitle("这是标题",
                      close_url=":/icon/close.png",
                      #close_url="../../resources/icon/img.png",
                      max_url=":/icon/max.png",
                      min_url=":/icon/min.png",
                      restore_url=":/icon/restore.png")
        self.contentWidget = QWidget()
        self.setWidget(self.contentWidget)
        self.contentWidget.setContentsMargins(0, 0, 0, 0)
        #基本布局
        self.__baseLayout = QHBoxLayout(self.contentWidget)
        #左面是导航,右面是内容

if __name__ == '__main__':
    app = QApplication()
    mainWindow = MainWindow()
    mainWindow.show()
    app.exec()

 

以下是使用PyQt实现自定义标题栏的示例代码: ```python from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor class CustomTitleBar(QWidget): def __init__(self, parent): super().__init__(parent) self.setFixedHeight(30) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.title_label = QLabel("Custom Title Bar") self.title_label.setAlignment(Qt.AlignCenter) layout.addWidget(self.title_label) self.minimize_button = QPushButton("-") self.minimize_button.setFixedSize(30, 30) self.minimize_button.clicked.connect(self.parent().showMinimized) layout.addWidget(self.minimize_button) self.maximize_button = QPushButton("□") self.maximize_button.setFixedSize(30, 30) self.maximize_button.clicked.connect(self.toggleMaximize) layout.addWidget(self.maximize_button) self.close_button = QPushButton("×") self.close_button.setFixedSize(30, 30) self.close_button.clicked.connect(self.parent().close) layout.addWidget(self.close_button) layout.addStretch() self.setStyleSheet(""" background-color: #333333; color: white; font-size: 14px; font-weight: bold; padding-left: 5px; padding-right: 5px; """) def toggleMaximize(self): if self.parent().isMaximized(): self.parent().showNormal() self.maximize_button.setText("□") else: self.parent().showMaximized() self.maximize_button.setText("◻") class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Custom Title Bar Example") self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground) self.title_bar = CustomTitleBar(self) self.setCentralWidget(QWidget(self)) self.centralWidget().setLayout(QVBoxLayout()) self.centralWidget().layout().addWidget(QLabel("Content Area")) self.setStyleSheet(""" QMainWindow { background-color: white; } """) self.show() app = QApplication([]) window = MainWindow() app.exec_() ``` 这段代码创建了一个自定义标题栏,其中包含了一个标题标签、最小化按钮、最大化/还原按钮和关闭按钮。通过设置窗口的`setWindowFlags(Qt.FramelessWindowHint)`和`setAttribute(Qt.WA_TranslucentBackground)`属性,使窗口无边框并且背景透明。然后,将自定义标题栏添加到窗口的顶部,并在窗口的中央添加一个内容区域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值