实战PyQt5:113-QSS定制窗口的标题栏
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实战PyQt5:113-QSS定制窗口的标题栏
定制标题栏
在前面的学习中可以看到,我们可以使用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_wi ndow_close.png</file> <file>images/icon_window_maximi ze.png</file> <file>images/icon_window_minimize.png</fi le> <file>images/icon_window_restore.png</file> </qres ource></RCC>
windowdragger.py:
from PyQt5.QtCore import pyqtSignal, QPointfrom PyQt5.Q tGui import QPainterfrom PyQt5.QtWidgets import QWidget, Q StyleOption, QStyle class WindowDragger(QWidget): doubleCl icked = pyqtSignal() def __init__(self, parent = None): su per(WindowDragger, self).__init__(parent) self.mousePr essed = False def mousePressEvent(self, event): self. mousePressed = True self.mousePos = event.globalPos() parent = self.parentWidget() if parent: parent = parent.parentWidget() if parent: self.wndPo s = parent.pos() def mouseMoveEvent(self, event): parent = self.parentWidget() if parent: parent = pare nt.parentWidget() if parent and self.mousePressed: parent.move(self.wndPos + (event.globalPos() - self.mouse Pos)) def mouseReleaseEvent(self, event): self.mousePre ssed = 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.QtC
ore import Qt, QEvent, QRect, QPointfrom PyQt5.QtGui import QIcon, QScreen, QColor, QPalette,QGuiApplicationfrom PyQt5.Q tWidgets import (QWidget, QApplication, QDesktopWidget, QG raphicsDropShadowEffect, QHBoxLayout, QVB oxLayout, QLabel, QToolButton, QSizePolicy, qApp) from windo wdragger import WindowDraggerimport resource_rc CONST_D RAG_BORDER_SIZE = 15 class FramelessWindow(QWidget): def __init__(self, parent = None): super(FramelessWindow,se lf).__init__(parent) self.mousePressed = False self.dr agTop = False self.dragLeft = False self.dragRight = Fals e self.dragBottom = False self.startGeometry = QRect() self.setWindowFlags(Qt.FramelessWindowHint | Qt.Win dowSystemMenuHint) #为了在Windows系统下正确处理最小化函数,需要加上最小化标志按钮 if platform.system() == 'Windows': self.setWindow Flags(self.windowFlags() | Qt.WindowMinimizeButtonHint) self.setAttribute(Qt.WA_NoSystemBackground, True) s elf.setAttribute(Qt.WA_TranslucentBackground) self.init Ui() #窗口阴影 windowShadow = QGraphicsDropShadowEffect() wi ndowShadow.setBlurRadius(9.0) windowShadow.setColor(se lf.palette().color(QPalette.Highlight)) windowShadow.setOffs et(0.0) self.windowFrame.setGraphicsEffect(windowShadow) self.setMouseTracking(True) #监测所有子窗口的鼠标移动事件 QApplication.instance().installEventFilter(self) def i nitUi(self): self.setObjectName('FramelessWindow') #关闭按钮 self.btnClose = QToolButton() self.btnClose.setSizePo licy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.btnClose.setIco
n(QIcon(':/images/icon_window_close.png')) self.btnClose.cli cked.connect(self.close) #最大化按钮 self.btnMaximize = QT oolButton() self.btnMaximize.s etSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.btnMa ximize.setIcon(QIcon(':/images/icon_window_maximize.png')) self.btnMaximize.clicked.connect(self.onButtonMaximizeClicke d) #最小化按钮 self.btnMinimize = QT oolButton() self.btnMinimize.se tSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.btnMini mize.setIcon(QIcon(':/images/icon_window_minimize.png')) self.btnMinimize.clicked.connect(lambda: self.setWindowState(Q t.WindowMinimized)) #恢复按钮 self.btnRestore = QT oolButton() self.btnRestore.setSi zePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.btnRestore .setIcon(QIcon(':/images/icon_window_restore.png')) self.bt nRestore.clicked.connect(self.onButtonRestoreClicked) #做边留空 spacer = QLabel() spacer.setFixedWidth(4) #左上角应用图标 self.icon = QLabel() self.icon.setFixedSize(16, 16) #中间标题信息 self.titleText = QLabel() self.titleText.setStyleSheet('bo rder: 0px none palette(base);') #标题条布局 layoutTitlebar = QHBoxLayout() layoutTitlebar.setCo ntentsMargins(1,1,1,1) layoutTitlebar.setSpacing(0) layo utTitlebar.addWidget(spacer) layoutTitlebar.addWidget(self.i con) layoutTitlebar.addWidget(self.titleText) layoutTitle bar.addWidget(self.btnMinimize) layoutTitlebar.addWidget( self.btnRestore) layoutTitlebar.addWidget(self.btnMaximize) layoutTitlebar.addWidget(self.btnClose) self.windo
wTitlebar = WindowDragger() self.windowTitlebar.setLayout (layoutTitlebar) self.windowTitlebar.doubleClicked.connect(s elf.titlebarDoubleClicked) #窗口内容部分 contentLayout = QVBoxLayout() contentLayout.setC ontentsMargins(0, 0, 0, 0) contentLayout.setSpacing(0) self.windowContent = QWidget() self.windowContent.set SizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self. windowContent.setLayout(contentLayout) self.window Frame = QWidget(self) frameLayout = QVBoxLayout() f rameLayout.setContentsMargins(0, 0, 0, 0) frameLayout.setS pacing(0) frameLayout.addWidget(self.windowTitlebar) frameLayout.addWidget(self.windowContent) self.windowFr ame.setLayout(frameLayout) #设置整个窗口的布局 layout = QVBoxLayout(self) layout.setContentsMargi ns(10, 10, 10, 10) layout.setSpacing(0) layout.addWidge t(self.windowFrame) self.setLayout(layout) self.btn Restore.setVisible(False) def onButtonRestoreClicked(self): self.btnRestore.setVisible(False) self.btnMaximize.setVisib le(True) yout().setContentsMargins(10, 10, 10, 10) self.setWindowState(Qt.WindowNoState) self.showNormal() def onButtonMaximizeClicked(self): self.btnMaximize.se tVisible(False) self.btnRestore.setVisible(True) yout ().setContentsMargins(0, 0, 0, 0) self.setWindowState(Qt.Win dowMaximized) self.showMaximized() def setCont ent(self, widget): yout().addWidget(wi dget) def setWindowTitle(self, text): self.titleText.setT ext(text) def setWindowIcon(self, ico): self.icon.setPix map(ico.pixmap(16,16)) def titlebarDou bleClicked(self): if self.isMaximized(): self.onButtonRe storeClicked() else: self.onButtonMaximizeClicked()
def mouseDoubleClickEvent(self, event): pass def ch eckBorderDragging(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 screenLi st: sz = QApplication.desktop().size() h = sz.h eight() w = sz.width() #右上角 if self.dragTop and self.dragRight: new_w = gl obalMousePos.x() - self.startGeometry.x() new_y = glo balMousePos.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 = gl obalMousePos.x() new_y = globalMousePos.y() if new_x > 0 and new_y > 0: new_geom = self.start Geometry new_geom.setX(new_x) new_g eom.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.set X(new_x) new_geom.setHeight(new_h) s elf.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(ne w_geom) elif self.dragLeft: new_x = globalMous ePos.x() if new_x > 0 and new_x < w - 50: n ew_geom = self.startGeometry new_geom.setX(new _x) self.setGeometry(new_geom) elif self.drag Right: new_w = globalMousePos.x() - self.startGeometr y.x() if new_w > 0: new_geom = self.startGe ometry new_geom.setWidth(new_w) ne w_geom.setX(self.startGeometry.x()) self.setGeometr y(new_geom) elif self.dragBottom: new_h = glo balMousePos.y() - self.startGeometry.y() if new_h > 0: new_geom = self.startGeometry new_geo m.setHeight(new_h) new_geom.setY(self.startGeome try.y()) self.setGeometry(new_geom) else: #没有鼠标按下 if self.leftBorderHit(globalMousePos) and self.topBorde rHit(globalMousePos): self.setCursor(Qt.SizeFDiagCurs or) elif self.rightBorderHit(globalMousePos) and self.topB orderHit(globalMousePos): self.setCursor(Qt.SizeBDiag Cursor) elif self.leftBorderHit(globalMousePos) and self.b ottomBorderHit(globalMousePos): self.setCursor(Qt.Siz eBDiagCursor) else: if self.topBorderHit(globalM ousePos): self.setCursor(Qt.SizeVerCursor) elif self.leftBorderHit(globalMousePos): self.setCurso r(Qt.SizeHorCursor) elif self.rightBorderHit(globalMous ePos): self.setCursor(Qt.SizeHorCursor) elif self.bottomBorderHit(globalMousePos): self.setCurs or(Qt.SizeVerCursor) else: self.dragTop = Fa lse self.dragLeft = False self.dragRight =
False self.dragBottom = False self.setCur sor(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.geo metry() tmp = rect.x() + rect.width() if pos.x() <= tmp a nd pos.x() >= (tmp - CONST_DRAG_BORDER_SIZE): retur n 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.geomet ry() tmp = rect.y() + rect.height() if pos.y() <= tmp and pos.y() >= (tmp - CONST_DRAG_BORDER_SIZE): return Tr ue return False def mousePressEvent(self, event): if self.isMaximized(): return self.mousePressed = T rue self.startGeometry = self.geometry() globalMo usePos = self.mapT oGlobal(QPoint(event.x(), event.y())) if self.leftBorderHit(globalMousePos) and self.topBorderHit(glo balMousePos): self.dragTop = True self.dragLeft = True self.setCursor(Qt.SizeFDiagCursor) elif self.rightB orderHit(globalMousePos) and self.topBorderHit(globalMouseP os): self.dragTop = True self.dragRight = True self.setCursor(Qt.SizeBDiagCursor) elif self.leftBorderHit(gl obalMousePos) and self.bottomBorderHit(globalMousePos): self.dragLeft = True self.dragBottom = True self. setCursor(Qt.SizeBDiagCursor) else: if self.topBorder Hit(globalMousePos): self.dragTop = True self. setCursor(Qt.SizeVerCursor) elif self.leftBorderHit(global MousePos): self.dragLeft = True self.setCursor (Qt.SizeHorCursor) elif self.rightBorderHit(globalMousePo
s): self.dragRight = True self.setCursor(Qt.Size HorCursor) elif self.bottomBorderHit(globalMousePos): self.dragBottom = True self.setCursor(Qt.SizeVe rCursor) def mouseReleaseEvent(self, event): if s elf.isMaximized(): return self.mousePressed = Fal se switchBackCursorNeeded = self.dragTop and self.dragLef t and self.dragRight and self.dragBottom self.dragTop = Fals e self.dragLeft = False self.dragRight = False self.dr agBottom = False if switchBackCursorNeeded: self.se tCursor(Qt.ArrowCursor) def eventFilter(self, watched, e vent): if self.isMaximized(): return QWidget.eventFilte r(self, watched, event) # 当鼠标在对象上移动时,检查鼠标移动事件 if event.type() == QEvent.MouseMove and event: self.checkBorderDragging(event) #只有在frame window 上时,才触发按下事件 elif event.type() == QEvent.MouseButtonPress and watch ed is self: if event: self.mousePressEvent(event) elif event.type() == QEvent.MouseButtonRelease: if se lf.mousePressed and event: self.mouseReleaseEvent(ev ent) return QWidget.eventFilter(self, watched, eve nt)
mainwindow.py:
from PyQt5.QtCore import Qt, QDateTime, QDate, QFilefro m PyQt5.QtGui import QPalette, QColorfrom PyQt5.QtWidgets i mport (QApplication, QWidget, QMainWindow, QPushButton, Q TextEdit, QGroupBox, QCheckBox, QRadioButt on, QComboBox, QLabel, QVBoxLayout, QHBo xLayout, QGridLayout, QStyleFactory, QTabWid get, QSizePolicy, QProgressBar, QTableWidget,
QLineEdit, QSpinBox, QDateTimeEdit, QSlider, QStatusBar, QScrollBar, QMenu, QMenuBar, QAction, QCalendar Widget, QDial) #标记控制窗口class Mainwindow(QMainWindow): def __init__(self): supe r(Mainwindow, self).__init__() #应用的初始调色板 self.origPalette = QApplication.palette() self.init Ui() def initUi(self): self.initMenuBar() #生成要显示的部件 self.createTopLeftGroupBox() self.createTopRightGro upBox() self.createBottomLeftTabWidget() self.createBot tomRightGroupBox() self.createProgressBar() main Layout = QGridLayout() mainLayout.addWidget(self.topLeft GroupBox, 1, 0) #1行0列 mainLayout.addWidget(self.topRightGroupBox, 1, 1) #1行1列 mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0) # 2行0列 mainLayout.addWidget(self.bottomRightGroupBox, 2, 1) #2行1列 mainLayout.addWidget(self.progressBar, 3, 0, 1, 2) ## 3行0列,占1行2列 mainLayout.setRowStretch(1, 1) mainLayout.setRowS tretch(2, 1) mainLayout.setColumnStretch(0, 1) mainLay out.setColumnStretch(1, 1) mainWidget = QWidget() mainWidget.setLayout(mainLayout) self.setCentral Widget(mainWidget) statusBar = QStatusBar() stat usBar.setSizeGripEnabled(True) self.setStatusBar(statusBar) #菜单栏设置 def initMenuBar(self): mBar = self.menuBar() menuFile = mBar.addMenu('文件
(&F)') aExit = QAction('退出(&X)', self) aExit.triggered.connect(QApplication.instance().q uit) 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.PartiallyCh ecked) layout = QVBoxLayout() layout.addWidget( rad1) layout.addWidget(rad2) layout.addWidget(rad3) layout.addWidget(chk) layout.addStretch(1) sel f.topLeftGroupBox.setLayout(layout) #创建右上角成组部件 def createTopRightGroupBox(self): self.topRightGroupB ox = QGroupBox('
组 2') btnDefault = QPushButton('Push Button:缺省模式
') btnDefault.setDefault(True) btnToggle = QPushB utton('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.bottomLeftTa bWidget = QTabWidget() self.bottomLeftTabWidget.setSize Policy(QSizePolicy.Preferred, QSizePolicy.Ignored) tab1
= QWidget() tableWidget = QTableWidget(10, 10) #10行10列 tab1Layout = QHBoxLayout() tab1Layout.setCo ntentsMargins(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.se tContentsMargins(5, 5, 5, 5) 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 = QHBoxLa yout() tab3Layout.setContentsMargins(5, 5, 5, 5) tab3La yout.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.bottomRigh tGroupBox = QGroupBox('
组 3') self.bottomRightGroupBox.setCheckable(True) sel f.bottomRightGroupBox.setChecked(True) lineEdit = Q LineEdit('s3cRe7') lineEdit.setEchoMode(QLineEdit.Password
) spinBox = QSpinBox(self.bottomRightGroupBox) spinBox.setValue(50) dateTimeEdit = QDateTimeEdit(s elf.bottomRightGroupBox) dateTimeEdit.setDateTime(QDat eTime.currentDateTime()) slider = QSlider(Qt.Horizont al, self.bottomRightGroupBox) slider.setValue(40) s crollBar = QScrollBar(Qt.Horizontal, self.bottomRightGroupBox) scrollBar.setValue(60) dial = QDial(self.bottomRigh tGroupBox) dial.setValue(30) dial.setNotchesVisible(Tru e) layout = QGridLayout() layout.addWidget(lineE dit, 0, 0, 1, 2) #0行0列,占1行2列 layout.addWidget(spinBox, 1, 0, 1, 2) #1行0列,占1行2列 layout.addWidget(dateTimeEdit, 2, 0, 1, 2) #2行0列,占1行2列 layout.addWidget(slider, 3, 0) #3行0列,占1行1列 layout.addWidget(scrollBar, 4, 0) #4行0列,占1行1列 layout.addWidget(dial, 3, 1, 2, 1) #3行1列,占2行1列 layout.setRowStretch(5, 1) self.bottomRightGro upBox.setLayout(layout) #禁止窗口上的组件 def setWidgetsDisbaled(self, disable): self.topLeftGroup Box.setDisabled(disable) self.topRightGroupBox.setDisabled (disable) self.bottomLeftTabWidget.setDisabled(disable) self.bottomRightGroupBox.setDisabled(disable) #创建进度
条 def createProgressBar(self): self.progressBar = QProgre ssBar() self.progressBar.setRange(0, 100) self.progressBa r.setValue(24)
fw_demo.py:
import sysfrom PyQt5.QtWidgets import QApplication,QSty lefrom mainwindow import Mainwindowfrom framelesswindow import FramelessWindowimport qdarkstyle if __name__ == '__m ain__': app = QApplication(sys.argv) #设置样式
表 app.setStyleSheet(qdarkstyle.load_stylesheet()) #创建一个无边框窗口 framelessWnd = FramelessWindow() framelessWnd.setWi ndowIcon(app.style().standardIcon(QStyle.SP_DesktopIcon)) fr amelessWnd.setWindowTitle('实战PyQt5: 自定义标题栏演示') #主窗口实例 window = Mainwindow() framelessWnd.setContent(wind ow) framelessWnd.show() sys.exit(app.exec()) 运行结果如下图:
定制标题栏
本文知识点
•使窗口的无边框模式来定制标题栏;
•无边框模式下窗口边框线绘制;
•无边框模式下处理窗口的移动,尺寸改变等。