【PyQt】重写系统标题栏

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

开发环境

  • 操作系统:Windows 10 x64 18363
  • Python:3.8.2 64-bit
  • Qt:PyQt5_5.14.1
  • IDE:Geany 1.34.1

纯 console 界面的 python 脚本实在是不太美观,且对用户不友好。所有就有开发 GUI 的需求,幸好 python 有内置的轻量化的 tkinter,对强大的 Qt 也有紧密支持的 PyQt

参考链接

  1. PyQt5之布局管理

  2. 样式控制-QSS 样式表

  3. Qss 渐变颜色设置

  4. QSS总结以及最近做的Qt项目

展望

Office Tool Plus

Office Tool Plus 为蓝本,实现 GUI 样式:

在这里插入图片描述

实现

  1. 去除系统标题栏
  2. 实现最大化、最小化及关闭窗口功能
  3. 【可选】实现窗口移动

去除标题栏

setWindowFlags(Qt.FramelessWindowHint)

设计 UI

这里我们使用 QWidget 加 QHBoxLayout 的布局,使用两个 QLabel(Icon、标题),三个 QPushButton(最小化、最大化、关闭)
【可选】换肤按钮、菜单按钮
在这里插入图片描述
这里使用 QT Designer 只是因为演示方便,实际可纯代码完成。

class OTitleBar(QWidget):
    """自定义标题栏

    实现了最大化与最小化,需配合 OWindow 使用"""

    def __init__(self, parent):
        super().__init__()
        # 设置尺寸策略
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.setFixedHeight(36)
        # ~ self.setAttribute(Qt.WA_TransparentForMouseEvents)  # 鼠标点击穿透

        self._parent = parent                 # 父(主)窗口
        self._menu_button = None              # 菜单按钮(QWidget)
        self._skin_button = None              # 换肤按钮(QWidget)
        self._title_icon = None               # 图标(QLabel)
        self.title_label = None               # 标题(QLabel)
        self._min_button = None               # 最小化按钮(QWidget)
        self._max_button = None               # 最大化按钮(QWidget)
        self._close_button = None             # 关闭按钮(QWidget)
        self._central_layout = None           # 主布局(水平)

        self._menu_icon = None                # 菜单图标
        self._skin_icon = None                # 换肤图标
        self._min_icon = None                 # 最小化按钮图标
        self._max_icon = None                 # 最大化按钮图标
        self._restore_icon = None             # 最大化按钮恢复图标
        self._close_icon = None               # 关闭按钮图标

        self._series = 'black'                # 图标样式(默认黑色)

        # 初始化标题栏属性
        self.init_titlebar()
        # 设置最窄宽度
        self.setMinimumWidth(self._title_icon.width() * 13)
        # 去除布局及控件间的间隔及边距
        set_all_gap(self._central_layout, left=8)
        self.setMouseTracking(True)
        self._title_icon.setMouseTracking(True)
        self.title_label.setMouseTracking(True)

    def init_titlebar(self):
        """初始化标题栏属性"""
        # 添加标题控件
        self._title_icon = QLabel()
        self.title_label = QLabel()
        self._min_button = QPushButton()
        self._max_button = QPushButton()
        self._close_button = QPushButton()
        self._title_icon.setObjectName('OTitleIcon')
        self.title_label.setObjectName('OTitleLabel')
        self._min_button.setObjectName('OMinButton')
        self._max_button.setObjectName('OMaxButton')
        self._close_button.setObjectName('OCloseButton')

        # 设置 icon
        set_icon(self._title_icon, ':/res/images/one_ccs.ico')
        self._load_icon()

        # 设置 icon 宽度
        self._title_icon.setFixedHeight(24)
        self._title_icon.setFixedWidth(24)

        # 设置标题控件策略
        expanding = QSizePolicy.Expanding
        self.title_label.setSizePolicy(expanding, expanding)

        # 设置窗口主布局,并放入主容器中
        self._central_layout = QHBoxLayout(self)

        # 把控件添加进标题栏布局
        self._central_layout.addWidget(self._title_icon, alignment=Qt.AlignVCenter)
        self._central_layout.addWidget(self.title_label, alignment=Qt.AlignVCenter)
        self._central_layout.addWidget(self._min_button, alignment=Qt.AlignVCenter)
        self._central_layout.addWidget(self._max_button, alignment=Qt.AlignVCenter)
        self._central_layout.addWidget(self._close_button, alignment=Qt.AlignVCenter)

        # 链接信号/槽
        self._min_button.clicked.connect(self.min_button_click)
        self._max_button.clicked.connect(self.max_button_click)
        self._close_button.clicked.connect(self.close_button_click)

    def set_menu_icon(self, icon):
        """设置菜单按钮图标"""
        if not isinstance(icon, QPixmap):
            icon = QPixmap(icon)

        icon = icon.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self._menu_icon = QIcon(QPixmap(icon))
        self._menu_button.setPixmap()
        self._menu_button.setIcon(self._menu_icon)

    def set_skin_icon(self, icon):
        """设置最换肤按钮图标"""
        self._skin_icon = QIcon(QPixmap(icon))
        self._skin_button.setIcon(self._skin_icon)

    def set_min_icon(self, icon):
        """设置最小化按钮图标"""
        self._min_icon = QIcon(QPixmap(icon))
        self._min_button.setIcon(self._min_icon)

    def set_max_icon(self, icon):
        """设置最大化按钮图标"""
        self._max_icon = QIcon(QPixmap(icon))
        self._max_button.setIcon(self._max_icon)

    def set_restore_icon(self, icon):
        """设置恢复按钮图标"""
        self._restore_icon = QIcon(QPixmap(icon))

    def set_close_icon(self, icon):
        """设置关闭按钮图标"""
        self._close_icon = QIcon(QPixmap(icon))
        self._close_button.setIcon(self._close_icon)

    def set_icon_series(self, series):
        """设置标题图标系列(黑/白)"""
        if series == 'black' or series == 'white':
            self._series = series
            self._load_icon()

    def add_menu_button(self):
        """添加菜单按钮"""
        self._menu_button = QPushButton()
        self.setObjectName('OMenuButton')
        self._menu_button.installEventFilter(self._parent)
        if self._series == 'black':
            self.set_menu_icon(':/res/images/title_menu.ico')
        elif self._series == 'white':
            self.set_menu_icon(':/res/images/title_menu_w.ico')
        self._central_layout.addWidget(self._menu_button)
        # 重新放置主按钮
        self._central_layout.addWidget(self._min_button)
        self._central_layout.addWidget(self._max_button)
        self._central_layout.addWidget(self._close_button)

        return self._menu_button

    def add_skin_button(self):
        """添加换肤按钮"""
        self._skin_button = QPushButton()
        self._skin_button.setObjectName('OSkinButton')
        self._skin_button.installEventFilter(self._parent)
        if self._series == 'black':
            self.set_skin_icon(':/res/images/title_skin.ico')
        elif self._series == 'white':
            self.set_skin_icon(':/res/images/title_skin_w.ico')
        # 设置图标大小
        # ~ self._skin_button.setIconSize(QSize(22, 22))
        self._central_layout.addWidget(self._skin_button)
        # 重新放置主按钮
        self._central_layout.addWidget(self._min_button)
        self._central_layout.addWidget(self._max_button)
        self._central_layout.addWidget(self._close_button)

        return self._skin_button

    def _load_icon(self):
        """设置图标"""

        res = ':/res/images/'
        if self._series == 'black':
            if self._menu_button:
                self.set_menu_icon(res + 'title_menu.ico')
            if self._skin_button:
                self.set_skin_icon(res + 'title_skin.ico')
            self.set_min_icon(res + 'title_min.ico')
            self.set_max_icon(res + 'title_max.ico')
            self.set_restore_icon(res + 'title_restore.ico')
            self.set_close_icon(res + 'title_close.ico')
        elif self._series == 'white':
            if self._menu_button:
                self.set_menu_icon(res + 'title_menu_w.ico')
            if self._skin_button:
                self.set_skin_icon(res + 'title_skin_w.ico')
            self.set_min_icon(res + 'title_min_w.ico')
            self.set_max_icon(res + 'title_max_w.ico')
            self.set_restore_icon(res + 'title_restore_w.ico')
            self.set_close_icon(res + 'title_close_w.ico')

    def _check_parent(self):
        """判断是否有父窗口"""
        if isinstance(self._parent, QWidget) or (
           isinstance(self._parent, QMainWindow)):
            return True
        else:
            return False

    # 槽函数
    def min_button_click(self):
        """最小化"""
        if self._check_parent():
            self._parent.showMinimized()
        else:
            self.showMinimized()

    def max_button_click(self):
        """最大化(恢复)"""
        if self._check_parent():
            if self._parent.isMaximized():
                self._parent.showNormal()
                self._max_button.setIcon(self._max_icon)
            else:
                self._parent.showMaximized()
                self._max_button.setIcon(self._restore_icon)

    def close_button_click(self):
        """关闭"""
        if self._check_parent():
            self._parent.close()
        else:
            self.close()


def set_icon(obj, icon, width=20, height=20):
    """为 QLabel 或 QPushButton 设置图像"""

    if not isinstance(obj, QLabel) and not isinstance(obj, QPushButton):
        logging.warning('set_icon(obj: Union[QLabel, QPushButton], icon,' +
                        ' width=20, height=20): argument 1 is unexpected' +
                        ' type {}'.format(type(obj)))
        return None

    KAR = Qt.KeepAspectRatio
    STFM = Qt.SmoothTransformation

    if isinstance(obj, QLabel):
        if isinstance(icon, str):
            icon = QPixmap(icon).scaled(width, height, KAR, STFM)

        obj.setPixmap(icon)
    else:
        if isinstance(icon, str):
            icon = QIcon(QPixmap(icon))
        if isinstance(icon, QPixmap):
            icon = QIcon(icon)

        obj.setIcon(icon)
        obj.setIconSize(QSize(width, height))

    return icon

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

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

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值