在前面的學(xué)習(xí)中可以看到,我們可以使用QSS為窗口的部件定義各種炫酷的效果,但是一個窗口的標(biāo)題欄始終是和所使用的操作系統(tǒng)相關(guān),在Win7下,是win7樣式的標(biāo)題欄,在Win10下是win10樣式的標(biāo)題欄,在Ubuntu下是Ubuntu樣式的標(biāo)題欄,Qt這樣設(shè)計,保持了應(yīng)用在不同的平臺下與相應(yīng)的系統(tǒng)的外觀上的統(tǒng)一。但是也帶來了一些問題,比如我們在窗口中使用深色主題,但是標(biāo)題欄一直保持淺色主題,看起來有些不協(xié)調(diào)。如果想在標(biāo)題欄和窗口統(tǒng)一使用一個主題樣式,則需要對標(biāo)題欄進(jìn)行定制處理。
在Qt下,可以將窗口設(shè)置成無標(biāo)題樣式,我們可以在無標(biāo)題樣式下,布局一個自己需要的標(biāo)題欄。定義對應(yīng)標(biāo)題欄按鈕功能,同時需要添加對窗口的移動,縮放等操作。為了達(dá)到上述目的和方便移植,定義類WindowDragger來作為標(biāo)題欄的父部件,它主要是處理一些鼠標(biāo)事件和繪圖事件。定義類FramelessWindow作為窗口的外框控件,使用其定義的setContent函數(shù)將要顯示的窗口放入其中,就可以輕松完成標(biāo)題欄的替換。
演示工程代碼文件包括:
代碼文件內(nèi)容分別如下:
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系統(tǒng)下正確處理最小化函數(shù),需要加上最小化標(biāo)志按鈕 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) #監(jiān)測所有子窗口的鼠標(biāo)移動事件 QApplication.instance().installEventFilter(self) def initUi(self): self.setObjectName('FramelessWindow') #關(guān)閉按鈕 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)) #恢復(fù)按鈕 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) #左上角應(yīng)用圖標(biāo) self.icon = QLabel() self.icon.setFixedSize(16, 16) #中間標(biāo)題信息 self.titleText = QLabel() self.titleText.setStyleSheet('border: 0px none palette(base);') #標(biāo)題條布局 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) #窗口內(nèi)容部分 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) #設(shè)置整個窗口的布局 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() #除開任務(wù)欄外可用的空間 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: #沒有鼠標(biāo)按下 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) # 當(dāng)鼠標(biāo)在對象上移動時,檢查鼠標(biāo)移動事件 if event.type() == QEvent.MouseMove and event: self.checkBorderDragging(event) #只有在frame window上時,才觸發(fā)按下事件 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) #標(biāo)記控制窗口class Mainwindow(QMainWindow): def __init__(self): super(Mainwindow, self).__init__() #應(yīng)用的初始調(diào)色板 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) #菜單欄設(shè)置 def initMenuBar(self): mBar = self.menuBar() menuFile = mBar.addMenu('文件(&F)') aExit = QAction('退出(&X)', self) aExit.triggered.connect(QApplication.instance().quit) menuFile.addAction(aExit) #創(chuàng)建左上角成組部件 def createTopLeftGroupBox(self): self.topLeftGroupBox = QGroupBox('組 1') rad1 = QRadioButton('單選按鈕1') rad2 = QRadioButton('單選按鈕2') rad3 = QRadioButton('單選按鈕3') rad1.setChecked(True) chk = QCheckBox('三態(tài)復(fù)選按鈕') 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) #創(chuàng)建右上角成組部件 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) #創(chuàng)建左下角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() #設(shè)置最小日期 calendar.setMinimumDate(QDate(1900,1,1)) #設(shè)置最大日期 calendar.setMaximumDate(QDate(4046,1,1)) #設(shè)置網(wǎng)格可見 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)') #創(chuàng)建右下角成組部件 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) #創(chuàng)建進(jìn)度條 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) #設(shè)置樣式表 app.setStyleSheet(qdarkstyle.load_stylesheet()) #創(chuàng)建一個無邊框窗口 framelessWnd = FramelessWindow() framelessWnd.setWindowIcon(app.style().standardIcon(QStyle.SP_DesktopIcon)) framelessWnd.setWindowTitle('實戰(zhàn)PyQt5: 自定義標(biāo)題欄演示') #主窗口實例 window = Mainwindow() framelessWnd.setContent(window) framelessWnd.show() sys.exit(app.exec())
運(yùn)行結(jié)果如下圖:
定制標(biāo)題欄
聯(lián)系客服