我们可以在以前的学习中使用它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, &
nbsp; 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(16, 16) #中间标题信息 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(0, 0, 0, 0) 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(0, 0, 0, 0) frameLayout.setSpacing(0) frameLayout.addWidget(self.windowTitlebar) frameLayout.addWidget(self.windowContent) self.windowFrame.setLayout(frameLayout) #设置整个窗口的布局 layout = QVBoxLayout(self) layout.setContentsMargins(10, 10, 10, 10) 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(10, 10, 10, 10) self.setWindowState(Qt.WindowNoState) self.showNormal() def onButtonMaximizeClicked(self): self.btnMaximize.setVisible(False) self.btnRestore.setVisible(True) self.layout().setContentsMargins(0, 0, 0, 0) 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) &路由知识nbsp; 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, 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.setRowStretch(2, 1) mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(1, 1) 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(10, 10) #10行10列 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(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 = QHBoxLayout() tab3Layout.setContentsMargins(5, 5, 5, 5) 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, 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.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(0, 100) 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())
运行结果如下图:
定制标题栏
本文知识点使窗口的无边框模式来定制标题栏;无边框模式下窗口边框线绘制;无边框模式下处理窗口的移动,尺寸改变等。喜欢的人,请多多关注,评论,收藏,点赞,和转发。