第二十八章 QSS
28.1 基本规则
28.2 选择器类型
28.3 子控件
28.4 伪状态
28.5 小结
QSS即Qt StyleSheet(Qt样式表)的简称,是一种用来自定义控件外观的强大机制。它的句法跟CSS非常相似,所以熟悉了CSS的读者可以很快的掌握本章内容。不熟悉的小伙伴可以仔细阅读本章,也算是对CSS语法的一种了解(爬虫中会用到CSS选择器)。
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查询所有的属性和值,子控件以及伪状态等: