打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
实战PyQt5: 113-QSS 定制窗口的标题栏
userphoto

2022.12.01 北京

关注

定制标题栏

在前面的学习中可以看到,我们可以使用QSS为窗口的部件定义各种炫酷的效果,但是一个窗口的标题栏始终是和所使用的操作系统相关,在Win7下,是win7样式的标题栏,在Win10下是win10样式的标题栏,在Ubuntu下是Ubuntu样式的标题栏,Qt这样设计,保持了应用在不同的平台下与相应的系统的外观上的统一。但是也带来了一些问题,比如我们在窗口中使用深色主题,但是标题栏一直保持浅色主题,看起来有些不协调。如果想在标题栏和窗口统一使用一个主题样式,则需要对标题栏进行定制处理。

在Qt下,可以将窗口设置成无标题样式,我们可以在无标题样式下,布局一个自己需要的标题栏。定义对应标题栏按钮功能,同时需要添加对窗口的移动,缩放等操作。为了达到上述目的和方便移植,定义类WindowDragger来作为标题栏的父部件,它主要是处理一些鼠标事件和绘图事件。定义类FramelessWindow作为窗口的外框控件,使用其定义的setContent函数将要显示的窗口放入其中,就可以轻松完成标题栏的替换。

测试

演示工程代码文件包括:

  • resource.qrc, 定义资源文件;
  • windowdragger.py, 实现类WindowDragger,控制标题栏的鼠标操作和绘图控制;
  • framelesswindow.py, 实现类FramelessWindow,实现一个带定制标题栏的框架;
  • mainwindow.py, 定义主窗口;
  • fw_demo.py 主程序,运行它,演示最终效果。
  • images/icon_window_close.png, 标题栏的关闭图标;
  • images/icon_window_maximize.png, 标题栏的最大化图标;
  • images/icon_window_minimize.png, 标题栏的最小化图标;
  • images/icon_window_restore.png, 标题栏的恢复图标。

代码文件内容分别如下:

resource.qrc:

<RCC>    <qresource prefix='/'>        <file>images/icon_window_close.png</file>        <file>images/icon_window_maximize.png</file>        <file>images/icon_window_minimize.png</file>        <file>images/icon_window_restore.png</file>    </qresource></RCC>

windowdragger.py:

from PyQt5.QtCore import pyqtSignal, QPointfrom PyQt5.QtGui import QPainterfrom PyQt5.QtWidgets import QWidget, QStyleOption, QStyle class WindowDragger(QWidget):    doubleClicked = pyqtSignal()        def __init__(self, parent = None):        super(WindowDragger, self).__init__(parent)                self.mousePressed = False            def mousePressEvent(self, event):        self.mousePressed = True        self.mousePos = event.globalPos()                parent = self.parentWidget()        if parent:            parent = parent.parentWidget()                    if parent:            self.wndPos = parent.pos()                def mouseMoveEvent(self, event):        parent = self.parentWidget()        if parent:            parent = parent.parentWidget()                    if parent and self.mousePressed:            parent.move(self.wndPos + (event.globalPos() - self.mousePos))        def mouseReleaseEvent(self, event):        self.mousePressed = False            def mouseDoubleClickEvent(self, event):        self.doubleClicked.emit()            def paintEvent(self, event):        styleOption = QStyleOption()        styleOption.initFrom(self)        painter = QPainter(self)        self.style().drawPrimitive(QStyle.PE_Widget, styleOption, painter, self)

framelesswindow.py:

import platform from PyQt5 import QtCorefrom PyQt5.QtCore import Qt, QEvent, QRect, QPointfrom PyQt5.QtGui import QIcon, QScreen, QColor, QPalette,QGuiApplicationfrom PyQt5.QtWidgets import (QWidget, QApplication, QDesktopWidget, QGraphicsDropShadowEffect,                             QHBoxLayout, QVBoxLayout, QLabel, QToolButton, QSizePolicy, qApp) from windowdragger import WindowDraggerimport resource_rc CONST_DRAG_BORDER_SIZE = 15 class FramelessWindow(QWidget):        def __init__(self, parent = None):        super(FramelessWindow,self).__init__(parent)                self.mousePressed = False        self.dragTop = False        self.dragLeft = False        self.dragRight = False        self.dragBottom = False        self.startGeometry = QRect()                self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)        #为了在Windows系统下正确处理最小化函数,需要加上最小化标志按钮        if platform.system() == 'Windows':            self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint)                    self.setAttribute(Qt.WA_NoSystemBackground, True)        self.setAttribute(Qt.WA_TranslucentBackground)                self.initUi()                #窗口阴影        windowShadow = QGraphicsDropShadowEffect()        windowShadow.setBlurRadius(9.0)        windowShadow.setColor(self.palette().color(QPalette.Highlight))        windowShadow.setOffset(0.0)        self.windowFrame.setGraphicsEffect(windowShadow)                       self.setMouseTracking(True)                #监测所有子窗口的鼠标移动事件        QApplication.instance().installEventFilter(self)            def initUi(self):              self.setObjectName('FramelessWindow')                #关闭按钮        self.btnClose = QToolButton()        self.btnClose.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)        self.btnClose.setIcon(QIcon(':/images/icon_window_close.png'))        self.btnClose.clicked.connect(self.close)        #最大化按钮        self.btnMaximize = QToolButton()        self.btnMaximize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)        self.btnMaximize.setIcon(QIcon(':/images/icon_window_maximize.png'))        self.btnMaximize.clicked.connect(self.onButtonMaximizeClicked)        #最小化按钮        self.btnMinimize = QToolButton()        self.btnMinimize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)        self.btnMinimize.setIcon(QIcon(':/images/icon_window_minimize.png'))        self.btnMinimize.clicked.connect(lambda: self.setWindowState(Qt.WindowMinimized))        #恢复按钮        self.btnRestore = QToolButton()        self.btnRestore.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)        self.btnRestore.setIcon(QIcon(':/images/icon_window_restore.png'))        self.btnRestore.clicked.connect(self.onButtonRestoreClicked)                #做边留空        spacer = QLabel()        spacer.setFixedWidth(4)        #左上角应用图标        self.icon = QLabel()        self.icon.setFixedSize(1616)        #中间标题信息        self.titleText = QLabel()        self.titleText.setStyleSheet('border: 0px none palette(base);')                      #标题条布局        layoutTitlebar = QHBoxLayout()        layoutTitlebar.setContentsMargins(1,1,1,1)        layoutTitlebar.setSpacing(0)        layoutTitlebar.addWidget(spacer)        layoutTitlebar.addWidget(self.icon)        layoutTitlebar.addWidget(self.titleText)        layoutTitlebar.addWidget(self.btnMinimize)        layoutTitlebar.addWidget(self.btnRestore)        layoutTitlebar.addWidget(self.btnMaximize)        layoutTitlebar.addWidget(self.btnClose)                self.windowTitlebar = WindowDragger()        self.windowTitlebar.setLayout(layoutTitlebar)        self.windowTitlebar.doubleClicked.connect(self.titlebarDoubleClicked)                #窗口内容部分         contentLayout = QVBoxLayout()        contentLayout.setContentsMargins(0000)         contentLayout.setSpacing(0)             self.windowContent = QWidget()        self.windowContent.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)        self.windowContent.setLayout(contentLayout)                self.windowFrame = QWidget(self)        frameLayout = QVBoxLayout()        frameLayout.setContentsMargins(0000)        frameLayout.setSpacing(0)        frameLayout.addWidget(self.windowTitlebar)        frameLayout.addWidget(self.windowContent)        self.windowFrame.setLayout(frameLayout)                #设置整个窗口的布局        layout = QVBoxLayout(self)        layout.setContentsMargins(10101010)        layout.setSpacing(0)        layout.addWidget(self.windowFrame)        self.setLayout(layout)                self.btnRestore.setVisible(False)        def onButtonRestoreClicked(self):        self.btnRestore.setVisible(False)        self.btnMaximize.setVisible(True)        self.layout().setContentsMargins(10101010)        self.setWindowState(Qt.WindowNoState)        self.showNormal()        def onButtonMaximizeClicked(self):        self.btnMaximize.setVisible(False)        self.btnRestore.setVisible(True)        self.layout().setContentsMargins(0000)        self.setWindowState(Qt.WindowMaximized)        self.showMaximized()                def setContent(self, widget):        self.windowContent.layout().addWidget(widget)            def setWindowTitle(self, text):        self.titleText.setText(text)            def setWindowIcon(self, ico):        self.icon.setPixmap(ico.pixmap(16,16))                                         def titlebarDoubleClicked(self):        if self.isMaximized():            self.onButtonRestoreClicked()        else:              self.onButtonMaximizeClicked()       def mouseDoubleClickEvent(self, event):        pass        def checkBorderDragging(self, event):        if self.isMaximized():            return                globalMousePos = event.globalPos()        if self.mousePressed:            screen = QGuiApplication.primaryScreen()            #除开任务栏外可用的空间            availGeometry = screen.availableGeometry()            h = availGeometry.height()            w = availGeometry.width()            screenList = screen.virtualSiblings()            if screen in screenList:                sz = QApplication.desktop().size()                h = sz.height()                w = sz.width()                        #右上角            if self.dragTop and self.dragRight:                new_w = globalMousePos.x() - self.startGeometry.x()                new_y = globalMousePos.y()                if new_w > 0 and new_y > 0 and new_y < h - 50:                    new_geom = self.startGeometry                    new_geom.setWidth(new_w)                    new_geom.setX(self.startGeometry.x())                    new_geom.setY(new_y)                    self.setGeometry(new_geom)                  #左上角            elif self.dragTop and self.dragLeft:                new_x = globalMousePos.x()                new_y = globalMousePos.y()                if new_x > 0 and new_y > 0:                    new_geom = self.startGeometry                    new_geom.setX(new_x)                    new_geom.setY(new_y)                    self.setGeometry(new_geom)                               #左下角            elif self.dragBottom and self.dragLeft:                new_h = globalMousePos.y() - self.startGeometry.y()                new_x = globalMousePos.x()                if new_h > 0 and new_x > 0:                    new_geom = self.startGeometry                    new_geom.setX(new_x)                    new_geom.setHeight(new_h)                    self.setGeometry(new_geom)                              elif self.dragTop:                new_y = globalMousePos.y()                if new_y > 0 and new_y < h - 50:                    new_geom = self.startGeometry                    new_geom.setY(new_y)                    self.setGeometry(new_geom)            elif self.dragLeft:                new_x = globalMousePos.x()                if new_x > 0 and new_x < w - 50:                    new_geom = self.startGeometry                    new_geom.setX(new_x)                    self.setGeometry(new_geom)            elif self.dragRight:                new_w = globalMousePos.x() - self.startGeometry.x()                if new_w > 0:                    new_geom = self.startGeometry                    new_geom.setWidth(new_w)                    new_geom.setX(self.startGeometry.x())                    self.setGeometry(new_geom)            elif self.dragBottom:                new_h = globalMousePos.y() - self.startGeometry.y()                if new_h > 0:                    new_geom = self.startGeometry                    new_geom.setHeight(new_h)                    new_geom.setY(self.startGeometry.y())                    self.setGeometry(new_geom)        else:            #没有鼠标按下            if self.leftBorderHit(globalMousePos) and self.topBorderHit(globalMousePos):                self.setCursor(Qt.SizeFDiagCursor)            elif self.rightBorderHit(globalMousePos) and self.topBorderHit(globalMousePos):                self.setCursor(Qt.SizeBDiagCursor)            elif self.leftBorderHit(globalMousePos) and self.bottomBorderHit(globalMousePos):                self.setCursor(Qt.SizeBDiagCursor)            else:                if self.topBorderHit(globalMousePos):                    self.setCursor(Qt.SizeVerCursor)                elif self.leftBorderHit(globalMousePos):                    self.setCursor(Qt.SizeHorCursor)                elif self.rightBorderHit(globalMousePos):                    self.setCursor(Qt.SizeHorCursor)                elif self.bottomBorderHit(globalMousePos):                    self.setCursor(Qt.SizeVerCursor)                else:                    self.dragTop = False                    self.dragLeft = False                    self.dragRight = False                    self.dragBottom = False                    self.setCursor(Qt.ArrowCursor)                        def leftBorderHit(self, pos):        rect = self.geometry()        if pos.x() >= rect.x() and pos.x() <= (rect.x() + CONST_DRAG_BORDER_SIZE):            return True        return False        def rightBorderHit(self, pos):        rect = self.geometry()        tmp = rect.x() + rect.width()        if pos.x() <= tmp and pos.x() >= (tmp - CONST_DRAG_BORDER_SIZE):            return True        return False        def topBorderHit(self, pos):        rect = self.geometry()        if pos.y() >= rect.y() and pos.y() <= (rect.y() + CONST_DRAG_BORDER_SIZE):            return True        return False        def bottomBorderHit(self, pos):        rect = self.geometry()        tmp = rect.y() + rect.height()        if pos.y() <= tmp and pos.y() >= (tmp - CONST_DRAG_BORDER_SIZE):            return True        return False        def mousePressEvent(self, event):        if self.isMaximized():            return                self.mousePressed = True        self.startGeometry = self.geometry()                globalMousePos = self.mapToGlobal(QPoint(event.x(), event.y()))                if self.leftBorderHit(globalMousePos) and self.topBorderHit(globalMousePos):            self.dragTop = True            self.dragLeft = True            self.setCursor(Qt.SizeFDiagCursor)        elif self.rightBorderHit(globalMousePos) and self.topBorderHit(globalMousePos):            self.dragTop = True            self.dragRight = True            self.setCursor(Qt.SizeBDiagCursor)        elif self.leftBorderHit(globalMousePos) and self.bottomBorderHit(globalMousePos):            self.dragLeft = True            self.dragBottom = True            self.setCursor(Qt.SizeBDiagCursor)        else:            if self.topBorderHit(globalMousePos):                self.dragTop = True                self.setCursor(Qt.SizeVerCursor)            elif self.leftBorderHit(globalMousePos):                self.dragLeft = True                self.setCursor(Qt.SizeHorCursor)            elif self.rightBorderHit(globalMousePos):                self.dragRight = True                self.setCursor(Qt.SizeHorCursor)            elif self.bottomBorderHit(globalMousePos):                self.dragBottom = True                self.setCursor(Qt.SizeVerCursor)                    def mouseReleaseEvent(self, event):        if self.isMaximized():            return                self.mousePressed = False        switchBackCursorNeeded = self.dragTop and self.dragLeft and self.dragRight and self.dragBottom        self.dragTop = False        self.dragLeft = False        self.dragRight = False        self.dragBottom = False        if switchBackCursorNeeded:            self.setCursor(Qt.ArrowCursor)              def eventFilter(self, watched, event):        if self.isMaximized():            return QWidget.eventFilter(self, watched, event)                # 当鼠标在对象上移动时,检查鼠标移动事件        if event.type() == QEvent.MouseMove and event:            self.checkBorderDragging(event)               #只有在frame window上时,才触发按下事件        elif event.type() == QEvent.MouseButtonPress and watched is self:            if event:                self.mousePressEvent(event)        elif event.type() == QEvent.MouseButtonRelease:            if self.mousePressed and event:                self.mouseReleaseEvent(event)                        return QWidget.eventFilter(self, watched, event)

mainwindow.py:

from PyQt5.QtCore import Qt, QDateTime, QDate, QFilefrom PyQt5.QtGui import QPalette, QColorfrom PyQt5.QtWidgets import (QApplication, QWidget, QMainWindow, QPushButton, QTextEdit,                              QGroupBox, QCheckBox, QRadioButton, QComboBox, QLabel,                             QVBoxLayout, QHBoxLayout, QGridLayout, QStyleFactory,                             QTabWidget, QSizePolicy, QProgressBar, QTableWidget,                             QLineEdit, QSpinBox, QDateTimeEdit, QSlider, QStatusBar,                             QScrollBar, QMenu, QMenuBar, QAction, QCalendarWidget, QDial) #标记控制窗口class Mainwindow(QMainWindow):    def __init__(self):        super(Mainwindow, self).__init__()                #应用的初始调色板        self.origPalette = QApplication.palette()                self.initUi()            def initUi(self):        self.initMenuBar()                       #生成要显示的部件        self.createTopLeftGroupBox()        self.createTopRightGroupBox()        self.createBottomLeftTabWidget()        self.createBottomRightGroupBox()        self.createProgressBar()                mainLayout = QGridLayout()        mainLayout.addWidget(self.topLeftGroupBox, 10) #10列        mainLayout.addWidget(self.topRightGroupBox, 11) #11列        mainLayout.addWidget(self.bottomLeftTabWidget, 20) #20列        mainLayout.addWidget(self.bottomRightGroupBox, 21) #21列        mainLayout.addWidget(self.progressBar, 3012) ## 30列,占12列        mainLayout.setRowStretch(11)        mainLayout.setRowStretch(21)        mainLayout.setColumnStretch(01)        mainLayout.setColumnStretch(11)                mainWidget = QWidget()        mainWidget.setLayout(mainLayout)                self.setCentralWidget(mainWidget)                statusBar = QStatusBar()        statusBar.setSizeGripEnabled(True)        self.setStatusBar(statusBar)               #菜单栏设置    def initMenuBar(self):        mBar = self.menuBar()                menuFile = mBar.addMenu('文件(&F)')                  aExit = QAction('退出(&X)', self)        aExit.triggered.connect(QApplication.instance().quit)        menuFile.addAction(aExit)         #创建左上角成组部件    def createTopLeftGroupBox(self):        self.topLeftGroupBox = QGroupBox('组 1')                rad1 = QRadioButton('单选按钮1')        rad2 = QRadioButton('单选按钮2')        rad3 = QRadioButton('单选按钮3')        rad1.setChecked(True)                chk = QCheckBox('三态复选按钮')        chk.setTristate(True)        chk.setCheckState(Qt.PartiallyChecked)                layout = QVBoxLayout()        layout.addWidget(rad1)        layout.addWidget(rad2)        layout.addWidget(rad3)        layout.addWidget(chk)        layout.addStretch(1)                self.topLeftGroupBox.setLayout(layout)            #创建右上角成组部件    def createTopRightGroupBox(self):        self.topRightGroupBox = QGroupBox('组 2')                btnDefault = QPushButton('Push Button:缺省模式')        btnDefault.setDefault(True)                btnToggle = QPushButton('Push Button: 切换模式')        btnToggle.setCheckable(True)        btnToggle.setChecked(True)                btnFlat = QPushButton('Push Button: 扁平外观')        btnFlat.setFlat(True)                layout = QVBoxLayout()        layout.addWidget(btnDefault)        layout.addWidget(btnToggle)        layout.addWidget(btnFlat)        layout.addStretch(1)                self.topRightGroupBox.setLayout(layout)                    #创建左下角Tab控件    def createBottomLeftTabWidget(self):        self.bottomLeftTabWidget = QTabWidget()        self.bottomLeftTabWidget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Ignored)                tab1 = QWidget()        tableWidget = QTableWidget(1010)  #1010列                tab1Layout = QHBoxLayout()        tab1Layout.setContentsMargins(5,5,5,5)        tab1Layout.addWidget(tableWidget)        tab1.setLayout(tab1Layout)                tab2 = QWidget()        textEdit = QTextEdit()        textEdit.setPlainText('一闪一闪小星星,\n'                              '我想知道你是什么.\n'                               '在整个世界之上, 如此的高,\n'                              '像在天空中的钻石.\n'                              '一闪一闪小星星,\n'                               '我多想知道你是什么!\n')                tab2Layout = QHBoxLayout()        tab2Layout.setContentsMargins(5555)        tab2Layout.addWidget(textEdit)        tab2.setLayout(tab2Layout)                tab3 = QWidget()        calendar = QCalendarWidget()        #设置最小日期        calendar.setMinimumDate(QDate(1900,1,1))        #设置最大日期        calendar.setMaximumDate(QDate(4046,1,1))        #设置网格可见        calendar.setGridVisible(True)        tab3Layout = QHBoxLayout()        tab3Layout.setContentsMargins(5555)        tab3Layout.addWidget(calendar)        tab3.setLayout(tab3Layout)                self.bottomLeftTabWidget.addTab(tab1, '表格(&T)')        self.bottomLeftTabWidget.addTab(tab2, '文本编辑(&E)')        self.bottomLeftTabWidget.addTab(tab3, '日历(&C)')                #self.bottomLeftTabWidget.addTab(tab1, '表格(&T)')        #self.bottomLeftTabWidget.addTab(tab2, '文本编辑(&E)')                    #创建右下角成组部件    def createBottomRightGroupBox(self):        self.bottomRightGroupBox = QGroupBox('组 3')        self.bottomRightGroupBox.setCheckable(True)        self.bottomRightGroupBox.setChecked(True)                lineEdit = QLineEdit('s3cRe7')        lineEdit.setEchoMode(QLineEdit.Password)                spinBox = QSpinBox(self.bottomRightGroupBox)        spinBox.setValue(50)                dateTimeEdit = QDateTimeEdit(self.bottomRightGroupBox)        dateTimeEdit.setDateTime(QDateTime.currentDateTime())                slider = QSlider(Qt.Horizontal, self.bottomRightGroupBox)        slider.setValue(40)                scrollBar = QScrollBar(Qt.Horizontal, self.bottomRightGroupBox)        scrollBar.setValue(60)                dial = QDial(self.bottomRightGroupBox)        dial.setValue(30)        dial.setNotchesVisible(True)                layout = QGridLayout()        layout.addWidget(lineEdit, 0012)  #00列,占12列        layout.addWidget(spinBox, 1012)   #10列,占12列        layout.addWidget(dateTimeEdit, 2012) #20列,占12列        layout.addWidget(slider, 30)  #30列,占11列        layout.addWidget(scrollBar, 40) #40列,占11列        layout.addWidget(dial, 3121)  #31列,占21列        layout.setRowStretch(51)                self.bottomRightGroupBox.setLayout(layout)              #禁止窗口上的组件    def setWidgetsDisbaled(self, disable):        self.topLeftGroupBox.setDisabled(disable)        self.topRightGroupBox.setDisabled(disable)        self.bottomLeftTabWidget.setDisabled(disable)        self.bottomRightGroupBox.setDisabled(disable)            #创建进度条    def createProgressBar(self):        self.progressBar = QProgressBar()        self.progressBar.setRange(0100)        self.progressBar.setValue(24)

fw_demo.py:

import sysfrom PyQt5.QtWidgets import QApplication,QStylefrom mainwindow import Mainwindowfrom framelesswindow import FramelessWindowimport qdarkstyle if __name__ == '__main__':    app = QApplication(sys.argv)        #设置样式表    app.setStyleSheet(qdarkstyle.load_stylesheet())     #创建一个无边框窗口    framelessWnd = FramelessWindow()    framelessWnd.setWindowIcon(app.style().standardIcon(QStyle.SP_DesktopIcon))    framelessWnd.setWindowTitle('实战PyQt5: 自定义标题栏演示')        #主窗口实例    window = Mainwindow()    framelessWnd.setContent(window)        framelessWnd.show()     sys.exit(app.exec())     

运行结果如下图:

定制标题栏

本文知识点

  • 使窗口的无边框模式来定制标题栏;
  • 无边框模式下窗口边框线绘制;
  • 无边框模式下处理窗口的移动,尺寸改变等。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
PyQt5快速入门(五)PyQt5布局管理
PyQt5基础知识 超详细!!!(含代码)
Python Rapid GUI Programming 第二篇。 30行写一个更奇葩的计...
PyQt4自定义控件----指示灯控件
【PyQt5
【pyqt5学习】
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服