《快速掌握PyQt5》第二十八章 QSS

第二十八章 QSS

28.1 基本规则

28.2 选择器类型

28.3 子控件

28.4 伪状态

28.5 小结


《快速掌握PyQt5》专栏已整理成书出版,书名为《PyQt编程快速上手》,详情请见该链接。感谢大家一直以来的支持!祝大家PyQt用得越来越顺!

QSS即Qt StyleSheet(Qt样式表)的简称,是一种用来自定义控件外观的强大机制。它的句法跟CSS非常相似,所以熟悉了CSS的读者可以很快的掌握本章内容。不熟悉的小伙伴可以仔细阅读本章,也算是对CSS语法的一种了解(爬虫中会用到CSS选择器)。

CSS可以让网页更美观,而QSS可以让我们的程序界面更加漂亮。(单押 ヘ( ̄ω ̄ヘ) ♪)

28.1 基本规则

每条QSS样式都由两部分组成:

1. 选择器,该部分指定要美化的控件

2. 声明,该部分指定要在控件上使用的属性和值

例如下面这条样式:

QPushButton {color: red}

QPushButton是设置的选择器,而大括号里的color: red分别为属性和值。这条样式会将所有的QPushButoon实例和它子类的文本颜色都设置为红色。我们直接来示范一下看下效果:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.button1 = QPushButton('super class', self)
        self.button2 = SubButton()

        self.h_layout = QHBoxLayout()
        self.h_layout.addWidget(self.button1)
        self.h_layout.addWidget(self.button2)
        self.setLayout(self.h_layout)


class SubButton(QPushButton):
    def __init__(self):
        super(SubButton, self).__init__()
        self.setText('subclass')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = 'QPushButton {color: red}'
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

在上面这个小程序中我们定义了两个类,第二个SubButton类继承自QPushButton并在Demo类中实例化。之后我们在程序入口处定义一条QSS,并调用setStyleSheet()方法设置整个程序的样式。

运行截图如下,可以发现子类也确实受到了影响:

可以声明多个属性和值,各对属性和值之间用分号分隔:

QPushButton {color: red; background-color: blue}

我们把程序中qss变量换成上面这句,我们发现按钮背景被设置成了蓝色:

当然也可以同时指定多个选择器:

QPushButton, QLabel, QLineEdit {color: red}

以上写法等同于:

QPushButton {color: red}
QLabel {color: red}
QLineEdit {color: red}

我们往程序中加入QLabel和QLineEdit控件看下效果:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QLineEdit, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.button = QPushButton('button', self)
        self.label = QLabel('label', self)
        self.label.setAlignment(Qt.AlignCenter)
        self.line_edit = QLineEdit(self)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.button)
        self.v_layout.addWidget(self.label)
        self.v_layout.addWidget(self.line_edit)
        self.setLayout(self.v_layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = 'QPushButton, QLabel, QLineEdit {color: red}'
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

运行截图如下,三个控件的文字颜色都变成了红色:

28.2 选择器类型

选择器不单单只有上面提到的那种写法,以下表格总结了最常用的几种选择器类型:

选择器类型

例子

解释

通用选择器 

*

匹配所有控件

类型选择器

QPushButton

匹配所有QPushButton实例及其子类(即上文中提到的那种)

属性选择器

QPushButton[name='btn']

QPushButton[name~='btn']

匹配所有name属性为btn的QPushButton实例。~=代表匹配所有name属性中包含btn的QPushButton实例

类选择器

.QPushButton

匹配所有QPushButton实例,但不匹配其子类。等同于

*[class~="QPushButton"]

ID选择器

QPushButton#okButton

匹配所有objectName为okButton的QPushButton实例

后代选择器

QDialog QPushButton

匹配所有QDialog控件中包含的QPushButton实例(无论是直接包含还是间接包含)

子选择器

QDialog > QPushButton

匹配所有QDialog控件中直接包含的QPushButton实例

我们来写一个QSS样式,把上面的选择器类型都包含进来,然后再放到程序中看下(旁边加上了解释):

* {color: red} # 所有控件的文本颜色都设为红色
QPushButton {background-color: blue} # 把所有QPushButton实例及其子类的背景颜色设为蓝色
QPushButton[name='btn'] {background-color: green} # 把所有name属性为btn的QPushButton实例的背景色设为绿色
.QLineEdit {font: bold 20px} # 把所有QLineEdit实例(不包括子类)的字体变粗,大小设为15px
QComboBox#cb {color: blue} # 把所有objectName为hello的下拉框文本颜色设为蓝色
QGroupBox QLabel {color: blue} # 把所有包含在QStackedWidget中的QLabel控件的文本颜色设为蓝色(无论直接包含还是间接包含)
QGroupBox > QLabel {font: 30px} # 把所有QStackedWidget直接包含的QLabel文本字体大小设为30px

代码如下:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QLineEdit, \
                            QComboBox, QStackedWidget, QGroupBox, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.button1 = QPushButton('button1', self)        # 1
        self.button2 = QPushButton('button2', self)
        self.button2.setProperty('name', 'btn')

        self.lineedit1 = QLineEdit(self)                   # 2
        self.lineedit1.setPlaceholderText('direct')
        self.lineedit2 = SubLineEdit()

        my_list = ['A', 'B', 'C', 'D']                     # 3
        self.combo = QComboBox(self)
        self.combo.addItems(my_list)
        self.combo.setObjectName('cb')

        self.gb = QGroupBox()                              # 4
        self.label1 = QLabel('label1')
        self.label2 = QLabel('label2')
        self.stack = QStackedWidget()
        self.stack.addWidget(self.label2)

        self.gb_layout = QVBoxLayout()
        self.gb_layout.addWidget(self.label1)
        self.gb_layout.addWidget(self.stack)
        self.gb.setLayout(self.gb_layout)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.button1)
        self.v_layout.addWidget(self.button2)
        self.v_layout.addWidget(self.lineedit1)
        self.v_layout.addWidget(self.lineedit2)
        self.v_layout.addWidget(self.combo)
        self.v_layout.addWidget(self.gb)
        self.setLayout(self.v_layout)


class SubLineEdit(QLineEdit):
    def __init__(self):
        super(SubLineEdit, self).__init__()
        self.setPlaceholderText('indirect')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = '''
          * {color: red}
          QPushButton {background-color: blue}
          QPushButton[name='btn'] {background-color: green}
          .QLineEdit {font: bold 20px}
          QComboBox#cb {color: blue}
          QGroupBox QLabel {color: blue}  
          QGroupBox > QLabel {font: 30px}
          '''
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

1. 实例化两个按钮,给button2添加name属性,并设为btn。这样button1的背景色就为蓝色,而button2的为绿色;

2. QLineEdit实例用于验证“类选择器”,笔者这里特地再写了一个SubLineEdit类并实例化来证明“类选择器”不会影响子类;

3. 实例化一个QComboBox控件,并调用setObjectName()方法将其objectName设置为cb;

4. 实例化一个QGroupBox控件,添加一个QLabel和一个QStackedWidget控件(后者又包含一个QLabel控件)。这里这样做是为了解释下直接包含和间接包含:在这里gb直接包含label1但间接包含label2。所以根据QSS样式,label1和label2的文本颜色都为蓝色,但只有直接包含的label1字体大小为30px

运行后我们发现达到期望效果(非常丑):

有些小伙伴可能就问了:* {color: red} 明明这条样式是让所有的文本颜色变为红色,但是有些控件的文本并没有根据这条来,为什么?

这里就涉及到一个“具体与笼统”的概念,当选择器写的越具体,选择器的优先程度越高。通配符*这一选择器写法非常笼统,而之后几条样式的选择器都是指定了控件名称的,比通配符更加具体,所以优先度更高。再比如这两条样式:

QPushButton {background-color: blue} 
QPushButton[name='btn'] {background-color: green}

第一条规定所有QPushButton实例及子类的背景色为蓝色,但第二条中指定了name属性,比第一条更加具体,所以背景色为绿色,并不按照第一条来。

28.3 子控件

先来举个栗子?来了解下什么是子控件:我们知道QComboBox由一个矩形框和一个下拉按钮组成,而这个下拉按钮就是QComboBox的子控件了,PyQt5允许我们使用QSS对其进行样式化。

请看下面程序示例:

import sys
from PyQt5.QtWidgets import QApplication, QComboBox


class Demo(QComboBox):
    def __init__(self):
        super(Demo, self).__init__()
        my_list = ['A', 'B', 'C', 'D']
        self.addItems(my_list)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = 'QComboBox::drop-down {image: url(drop-down.png)}'  # 1
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

1. 通过在控件名后面加两个英文状态下的冒号::然后再写要获取的子控件drop-down就可以了。在这里我们把QComboBox的子控件的图片换成了我们自已定义的(注意将drop-down.png图片文件放到项目目录中)。

图片下载地址(下面还会用到):https://www.easyicon.net/download/png/526255/24/

运行截图如下:

我们再用QSpinBox示范下,原始控件长这样,子控件由一个上调按钮和一个下调按钮组成:

我们接下来把子控件的按钮给换掉,程序如下:

import sys
from PyQt5.QtWidgets import QApplication, QSpinBox


class Demo(QSpinBox):
    def __init__(self):
        super(Demo, self).__init__()
        self.setMinimum(0)
        self.setMaximum(100)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = """
          QSpinBox::up-button {image: url(up-arrow.png)}
          QSpinBox::down-button {image: url(down-arrow.png)}  
          """
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

图片下载地址:

up-arrow.png: https://www.easyicon.net/download/png/526252/24/

down-arrow.png: https://www.easyicon.net/download/png/526255/24/ 之前那张

运行截图如下:

28.4 伪状态

伪状态选择器可以让我们针对某控件的不同状态进行QSS样式化操作,下面我们以QPushButton和QComboBox为例:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QComboBox, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.button1 = QPushButton('button1', self)
        self.button2 = QPushButton('button2', self)
        self.button2.setProperty('name', 'btn2')

        my_list = ['A', 'B', 'C', 'D']
        self.combo = QComboBox(self)
        self.combo.addItems(my_list)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.button1)
        self.v_layout.addWidget(self.button2)
        self.v_layout.addWidget(self.combo)
        self.setLayout(self.v_layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    qss = """
          QPushButton:hover {background-color: red}                     
          QPushButton[name='btn2']:pressed {background-color: blue}
          QComboBox::drop-down:hover {background-color: green}
          """
    demo.setStyleSheet(qss)
    demo.show()
    sys.exit(app.exec_())

伪状态选择器写法就是在控件名后加一个英文状态下的冒号:,再加上伪状态即可。以下是对三条QSS样式的解释:

QPushButton:hover {background-color: red} # 当鼠标悬停在QPushButton实例或其子类上时,将背景变为红色                    
QPushButton[name='btn2']:pressed {background-color: blue} # 当鼠标在QPushButton实例或其子类上按下时,将背景变为蓝色(但只针对name属性为btn2的QPushButton实例及子类)
QComboBox::drop-down:hover {background-color: green} # 当鼠标在QComboBox实例或其子类的下拉按钮子控件上悬停时,将下拉按钮的背景色改为绿色

运行截图如下,首先是原始状态:

接下来把鼠标悬停在button1上:

然后然button2处于按下状态:

最后让鼠标悬停在QComboBox的下拉框上:

我们知道如果在if判断语句中加个感叹号!就表示相反(否),那我们也可以在伪状态前加上这个感叹号!来表示相反状态。比如:

QPushButton:!hover {background-color: red}

那此时这条样式就会这样解释:当鼠标不悬停在QPushButton实例或其子类上时,背景颜色才会是红色。

小伙伴可以自己试验下。

28.5 小结

1. QSS样式用的好,可以让简单的程序也变得高大上起来,用户的好感度也会上去;

2. 读者可以在Qt Assistant中输入Qt Style Sheets Reference查询所有的属性和值,子控件以及伪状态等:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

la_vie_est_belle

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值