PyQt5快速入门(六)PyQt5 GUI界面设计

一、窗口风格

1、设置窗口风格

Qt实现的窗口样式默认使用的是当前操作系统的原生窗口样式,在不同操作系统下原生窗口样式显示的风格是不一样的。
可以为每个Widget设置风格:
setStyle(QStyle style)
获取当前平台支持的原有QStyle样式
QStyleFactory.keys()
对QApplication设置QStyle样式
QApplication.setStyle(QStyleFactory.create(“WindowsXP”))
如果其它Widget没有设置QStyle,则默认使用QApplication使用的QStyle。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.layout = QHBoxLayout()
        label = QLabel("Set Style:",self)
        combo = QComboBox(self)
        combo.addItems(QStyleFactory.keys())
        # 选择当前窗口风格
        index = combo.findText(QApplication.style().objectName(), Qt.MatchFixedString)
        # 设置当前窗口风格
        combo.setCurrentIndex(index)
        combo.activated[str].connect(self.onCurrentIndexChanged)

        self.layout.addWidget(label)
        self.layout.addWidget(combo)

        self.setLayout(self.layout)
        self.setWindowTitle("Application Style")
        self.resize(300, 100)

    def onCurrentIndexChanged(self, style):
        QApplication.setStyle(style)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

2、设置窗口样式

PyQt5使用setWindowFlags(Qt.WindowFlags)函数设置窗口样式。
Qt的窗口类型如下:
Qt.Widget:默认窗口,由最大化、最小化、关闭按钮
Qt.Window:普通窗口,有最大化、最小化、关闭按钮
Qt.Dialog:对话框窗口,有问号和关闭按钮
Qt.Popup:弹出窗口,窗口无边框
Qt.ToolTip:提示窗口,窗口无边框,无任务栏
Qt.SplashScreen:闪屏,窗口无边框,无任务栏
Qt.SubWindow:子窗口,窗口无按钮,但有标题栏
Qt自定义的顶层窗口外观标识:
Qt.MSWindowsFixedSizeDialogHint:窗口无法调整大小
Qt.FrameLessWindowHint:窗口无边框
Qt.CustomWinodwHint:有边框但无标题栏和按钮,不能移动和拖动
Qt.WindowTitleHint:添加一个标题栏和一个关闭按钮
Qt.WindowSystemMenuHint:添加系统目录和一个关闭按钮
Qt.WindowMaximizeButtonHint:激活最大化和关闭按钮,禁止最小化按钮
Qt.WindowMinimizeButtonHint:激活最小化和关闭按钮,禁止最大化按钮
Qt.WindowMinMaxButtonsHint:激活最大化、最小化和关闭按钮
Qt.WindowCloseButtonHint:增加一个关闭按钮
Qt.WindowContextHelpButtonHint:增加问号和关闭按钮
Qt.WindowStaysOnTopHint:窗口始终处于顶层位置
Qt.WindowStaysOnBottomHint:窗口始终处于底层位置

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.setWindowFlags(Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint)
        self.resize(400, 200)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3、自定义窗口

自定义一个无边框、铺满整个显示屏的窗口实现如下:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.setWindowFlags(Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint)
        self.setStyleSheet("background-color:blue;")

    def showMaximized(self):
        # 获取桌面控件
        desktop = QApplication.desktop()
        # 获取屏幕可显示矩形区域
        rect = desktop.availableGeometry()
        # 设置窗口尺寸
        self.setGeometry(rect)
        self.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.showMaximized()

    sys.exit(app.exec_())

二、绘图

1、图像类

Qt中有四个图像类,QPixmap、QImage、QPicture、QBitmap。
QPixmap专为绘图而设计,在绘制图片时需要QPixmap。
QImage提供了一个与硬件无关的图像表示函数,可以用于图片的像素级访问。
QPicture是绘图设备类,继承自QPainter,可以使用QPainter的begin函数在QPicture上绘图,使用end结束绘图,使用QPicture的save函数将QPainter使用过的绘图指令保存到文件中。
QBitmap是一个继承自QPixmap的便利类,提供了1bit深度的二值图像的类,提供的单×××像可以用来制作游标QCursor或者画刷QBrush。

2、绘图

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.pix = QPixmap(400, 400)
        self.pix.fill(Qt.white)
        self.lastPoint = QPoint()
        self.endPoint = QPoint()
        self.setWindowFlags(Qt.WindowTitleHint)
        self.resize(400, 400)

    def paintEvent(self, event):
        painter = QPainter(self.pix)
        painter.drawLine(self.lastPoint, self.endPoint)
        self.lastPoint = self.endPoint
        painter = QPainter(self)
        painter.drawPixmap(0, 0, self.pix)

    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton:
            self.endPoint = event.pos()
            self.update()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.lastPoint = event.pos()
            self.endPoint = self.lastPoint

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.endPoint = event.pos()
            self.update()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3、双缓冲绘图

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.pix = QPixmap(400, 400)
        self.pix.fill(Qt.white)
        self.lastPoint = QPoint()
        self.endPoint = QPoint()
        self.setWindowFlags(Qt.WindowTitleHint)
        self.resize(400, 400)

    def paintEvent(self, event):
        painter1 = QPainter(self)
        x = self.lastPoint.x()
        y = self.lastPoint.y()
        w = self.endPoint.x() - x
        h = self.endPoint.y() - y
        # 将图形会值在PixMap画布
        painter2 = QPainter(self.pix)
        painter2.drawRect(x, y, w, h)
        # 将pximap绘制到窗口
        painter1.drawPixmap(0, 0, self.pix)

    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton:
            self.endPoint = event.pos()
            self.update()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.lastPoint = event.pos()
            self.endPoint = self.lastPoint

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.endPoint = event.pos()
            self.update()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

上述代码中,首先使用painter将图形绘制在Pixmap上,然后将Pixmap绘制到窗口中。在拖动鼠标绘制矩形的过程中会出现重影,鼠标拖动过程中,屏幕会刷新很多次,paintEvent函数会被执行很多次,每次都会绘制一个矩形。
为了避免重影,可以使用双缓冲绘制技术。即使用两块画布,pix和tempPix,tempPix作为临时缓冲区,当拖动鼠标绘制矩形时,将内容先绘制到tempPix上,然后再将tempPix绘制到界面窗口。当释放鼠标完成矩形绘制时,将tempPix的内容复制到pix缓冲区,最终完成绘制到窗口。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.pix = QPixmap(400, 400)
        self.pix.fill(Qt.white)
        self.lastPoint = QPoint()
        self.endPoint = QPoint()
        self.setWindowFlags(Qt.WindowTitleHint)
        self.resize(400, 400)
        self.isDrawing = False
        self.tempPix = QPixmap()

    def paintEvent(self, event):
        painter = QPainter(self)
        x = self.lastPoint.x()
        y = self.lastPoint.y()
        w = self.endPoint.x() - x
        h = self.endPoint.y() - y
        if self.isDrawing:
            self.tempPix = self.pix
            pp = QPainter(self.tempPix)
            pp.drawRect(x, y, w, h)
            painter.drawPixmap(0, 0, self.tempPix)
        else:
            pp = QPainter(self.pix)
            pp.drawRect(x, y, w, h)
            painter.drawPixmap(0, 0, self.pix)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.lastPoint = event.pos()
            self.endPoint = self.lastPoint
            self.isDrawing = True

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.endPoint = event.pos()
            self.update()
            self.isDrawing = False

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

三、QSS样式设置

1、QSS语法规则

QSS(Qt Style Sheets),即Qt样式表,是用来自定义控件外观的一种机制。QSS语法规则与CSS大体相同,QSS样式由两部分组成,选择器用于指定哪些控件受到影响,声明用于指定哪些属性应该在控件上进行设置。声明部分是一系列的“属性:值”,使用分号分隔各个不同的属性值对,使用大括号将所有的声明包括在内。
QPushButton{color:red;}
表示设置QPushButton及其子类所有实例的前景色为红色,QPushButton表示选择器,指定所有的QPushButton类及其子类都会受到影响。
可以使用多个选择器指定相关的声明,使用逗号将各个选择器分隔,如QPushButton,QLineEdit{color:red;}

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        button = QPushButton("Button")
        self.layout = QHBoxLayout()
        self.layout.addWidget(button)
        self.setLayout(self.layout)
        qss = "QPushButton{color:red;}"
        self.setStyleSheet(qss)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

2、QSS选择器类型

QSS选择器有如下几种类型:
1、通配选择器:*,匹配所有的控件
2、类型选择器,如QPushButton,用于匹配所有的类及其子类的实例。
3、属性选择器:匹配所有的属性及属性值的实例,如QPushButton[name=”okButton”]将匹配所有的name属性为okButton的按钮实例。
4、类选择器:.ClassName,如.QPushButton用于匹配所有的QPushButton实例,但不匹配其子类。
5、ID选择器:#objectName,如#okButton匹配所有的ID为okButton的控件,ID即为obejctName。
6、后代选择器:如QDialog QPushButton,匹配所有的QDialog容器中包含的QPushButton,不管是直接的还是间接的。
7、子选择器:如QDialog > QPushButton,匹配所有的QDialog容器中包含的QPushButton,要求QPushButton的直接父容器是QDialog。
3、QSS子控件
QSS子控件是一种选择器,通常应用在复杂控件上,如QComboBox,QComboBox有一个矩形的外边框,右边有一个下拉箭头,点击后会弹出下拉列表。
::drop-down子控件选择器可以与选择器组合使用,
QComboBox#combo::drop-down{imge:url(dropdown.png)}表示为指定ID为combo的QComboBox控件的下拉箭头自定义图标。

4、QSS伪状态

QSS伪状态选择器是以:开头的一个选择表达式,如:hover,表示当鼠标经过时的状态。伪状态选择器限制了当控件处于某种状态时才可以使用的QSS规则,伪状态只能描述一个控件或复合控件的子控件状态,因此只能放在选择器的最后面。
QComboBox::drop-down:hover{background-color:red;}

5、QDarkStyleSheet

QDarkStyleSheet是一个用于PyQt应用的深黑色样式表。
GitHub:
https://github.com/ColinDuquesnoy/QDarkStyleSheet

四、设置窗口背景

窗口背景主要包括背景色和背景图片。设置窗口背景主要有三种方法:QSS设置窗口背景;QPalette设置窗口背景;paintEvent函数内部使用QPainter绘制窗口背景。

1、QSS设置窗口背景

通过QSS可以设置窗口的背景色或背景图片。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setObjectName("mainWindow")
        qss = "QWidget#mainWindow{background-color:black;}"
        # qss = "QWidget#mainWindow{border-image:url(background.png);}"
        self.setStyleSheet(qss)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

2、QPalette设置窗口背景

使用QPalette设置窗口背景颜色。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        palette = QPalette()
        palette.setColor(QPalette.Background, Qt.black)
        self.setPalette(palette)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

使用QPalette设置窗口背景图片时,需要考虑背景图片的尺寸,当背景图片的宽度和高度大于窗口的宽度和高度时,背景图片将会平铺整个窗口;当背景图片的宽度和高度小于窗口的宽度和高度时,则加载多个背景图片。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        palette = QPalette()
        palette.setBrush(QPalette.Background, QBrush(QPixmap("background.png")))
        self.setPalette(palette)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3、paintEvent绘制窗口背景

在paintEvent函数内部绘制背景色:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setBrush(Qt.black)
        painter.drawRect(self.rect())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

在paintEvent函数内部绘制背景图片:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")

    def paintEvent(self, event):
        painter = QPainter(self)
        pixmap = QPixmap("background.png")
        painter.drawPixmap(self.rect(), pixmap)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

五、样式设置实例

1、为标签添加背景图片

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.layout = QVBoxLayout()
        label = QLabel()
        label.setStyleSheet("QLabel{border-image:url(background.png);}")
        label.setFixedHeight(400)
        label.setFixedWidth(600)
        self.layout.addWidget(label)
        self.setLayout(self.layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

2、为按钮添加背景图片

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.layout = QVBoxLayout()
        button = QPushButton("Button")
        button.setMaximumSize(60, 30)
        button.setObjectName("okButton")
        button.setStyleSheet("QPushButton#okButton{border-image:url(background.png);}")

        self.layout.addWidget(button)
        self.setLayout(self.layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3、缩放图片

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.layout = QVBoxLayout()
        label = QLabel()
        img = QImage("background.png")
        result = img.scaled(label.width(), label.height(),Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
        label.setPixmap(QPixmap.fromImage(result))

        self.layout.addWidget(label)
        self.setLayout(self.layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

4、设置窗口透明

如果窗口是透明的,通过窗口可以看到桌面,要想实现窗口的透明效果,需要设置窗口透明度。透明度取值范围为0(完全透明)到1(不透明)。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow")
        self.setWindowOpacity(0.5)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

5、加载QSS

Qt中,为了降低耦合性,将UI代码与业务逻辑代码进行分离,通常会定义QSS文件,编写各种控件的样式,最后使用QApplication.setStyleSheet或QWidget.setStyleSheet设置QSS。
通常编写QSS文件需要加载到qrc资源文件中,然后编写一个公用类加载QSS。

class CommonHelper(object):
    @staticmethod
    def setStyleSheet(filename):
        if filename is not None:
            style = ""
            with open(filename, "r") as f:
                style = f.read()
            qApp.setStyleSheet(style)