QSS样式表简介
在Qt中,提供了一种叫做Qt样式表(简称QSS)的自定义控件外观机制,QSS的思想很大程度上是来自于HTML的层叠式样式表(CSS), 它大量参考了CSS的内容,其语法也和CSS类似,使用QSS可以使界面美化部分和代码层分开,可以在不修改代码的情况下改变应用的外观显示,这种分离的方式大大方便了应用的开发维护。
通过调用QWidget.setStyleSheet()或QApplication.setStyleSheet(), 你可以为一个独立的子部件、整个窗口,甚至是整个个应用程序指定一个样式表。
样式表是通过QStyle的一个叫做QStyleSheetStyle的特殊子类来实现的。 这个特殊的子类实际上是其他的系统特定风格类的包裹类, 它会把通过样式表指定的自定义外观风格应用在底层的系统特定风格之上。
语法样式
QSS的语法规则几乎与CSS相同, QSS样式由两部分组成:其中一部分称为选择器(Selector), 它指定程序中哪些类的控件受到影响。另一部分则是声明(Declaration),它指定哪些属性应该在控件上进行设置。
一个样式表由一系列的样式规则构成。每个样式规则都有着下面的形式:
selector { attribute: value }
选择器selector部分通常是一个类名(如QPushButton), 属性(attribute)部分是一个样式属性的名字,值(value)部分是赋给该属性的值。例如:
QPushButton { background-color: green}
表示设置QPushButton类及其子类的所有实例的背景色是绿色,其中,QPushButton表示选择器,指定所有的QPushButton类及其子类都会受到影响,注意,凡是继承自QPushButton的子类都会受到影响,这是与CSS不同的地方,因为CSS应用的都是一些标签,没有类的结构,更没有子类的概念,{ background-color: green }则是规则的定义,表示指定背景色是绿色。
为了使用方便,我们可以使用一种简化样式:
selector1, selector2, ..., selectorM {
attribute1: value1;
attribute2: value2;
...
attributeN: valueN;
}
这种简化形式可以同时为M个选择器相匹配是部件设置N种属性。例如:
QCheckBox, QComboBox, QSpinBox {
color: red;
background-color: white;
font: bold;
}
上述规则设置了所有的QCheckBox、QComboBox和QSpinBox的前景色、背景色和字体。
注意:除了Qt的类名、对象名和Qt属性名以外, QSS样式表通常不区分大小写。
盒模型(Box Model)简介
使用样式表时,每个小部件都被视为具有四个同心矩形的框:边距矩形(margin rectangle),边框矩形(border rectangle),填充矩形(padding rectangle)和内容矩形(contect rectangle)。盒模型的四个同心矩形在概念上显示如下图所示:
图片来源:doc.qt.io
在默认情况下,边缘,边框宽度和填充属性默认为0。这样四个矩形(margin, border, padding和content)重合。
可以使用background-image属性为部件指定背景。默认情况下,仅在边框内的区域绘制背景图像。可以使用background-clip属性更改此设置。可以使用background-repeat和background-origin来控制背景图像的重复和来源。
背景图像无法随窗口部件的大小缩放。要提供与部件尺寸一起缩放的“外观”或背景,必须使用border-image。由于border-image属性提供了备用背景,因此在指定border-image时不需要指定背景图像。如果同时指定了两者,则边框图像border-image将覆盖背景图像background-image。
QSS选择器类型
QSS选择器有以下几种类型:
- 通配选择器 * 通配所有控件;
- 类型选择器 QPushButton 匹配QPushButton及其子类
- 属性选择器 QPushButton[name='mybtn'] 匹配所有的name属性是myBtn的QPushButton实例。注意,该属性是可以自定义的,不一定非得是类本身具有的属性
- 类选择器 .QPushButton 匹配QPushButton的实例,但不匹配其子类的实例。
- ID选择器 QPushButton#okButton 匹配对象名称为okButton的所有QPushButton实例。这里的ID实际上就是objectName指定的值
- 后代选择器 QDialog QPushButton 只匹配QDialog中的所有层级的QPushbutton的样式(这种层级是指qobject树所表示的层级)
- 子选择器 QDialog > QPushButton 只匹配QDialog中的第一层级的QPushbutton的样式(这种层级是指qobject树所表示的层级)
处理伪状态
部件的外观可以按照用户界面元素状态的不同来分别定义,这在样式表中被称为“位状态”。伪状态出现在选择器的末尾,中间用冒号(:)分隔。如:
QPushButton:hover { color: white }
表示当鼠标悬停在按钮上时颜色为白色。
伪状态可以使用感叹号!操作符进行否定。如:
QPushButton:!hover{color: red}
表示当鼠标不是悬停在按钮上时颜色为红色
多个伪状态可以一起使用。如:
QCheckBox:hover:checked{color:white}
表示当鼠标悬停在复选框上并且该复选框被选中的时候颜色为白色。
QCheckBox:hover,QCheckBox:checked{color:white)
表示当鼠标悬停在复选框上或者该复选框被选中的时候颜色为白色。
下面是一些可用的伪状态:
- :checked button部件被选中
- :disabled 部件被禁用
- :enabled 部件被启用
- :hover 鼠标悬停在部件之上
- :indeterminate checkbox或radiobutton被部分选中
- :off 部件可以切换,且处于off状态
- :on 部件可以切换,且处于on状态
- :pressed 鼠标按下部件
- :unchecked button部件未被选中
子部件样式设置
许多部件都包含有子元素,这些元素可以称为“子部件”。Spin box的上下箭头就是子部件最好的例子。
子部件可以通过::来指定,例如QDateTimeEdit::up-button。定义子部件的样式与定义部件非常相似,它们遵循前面提到的盒模型(即它们可以拥有自己的边框、背景等),并且也可以和伪状态联合使用(例如QSpinBox::up-button:hover)。
下面列出一些可用的子部件类型:
- ::down-arrow combo box或spin box的下拉箭头
- ::down-button spin box的向下按钮
- ::drop-down combo box的下拉箭头
- ::indicator checkbox、radio button或可选择group box的指示器
- ::item menu、menu bar或status bar的子条目
- ::menu-indicator push button的菜单指示器
- ::title group box的标题
- ::up-arrow spin box的向上箭头
- ::up-button spin box的向上按钮
通过指定subcontrol-position和subcontrol-origin属性,子部件可以被放置在部件内的任何位置。并且子部件的位置还可以使用相对或绝对的方式进一步的调整。具体选择何种调整方式取决于子部件具有固定的大小,还是会随着父部件而变化。
1. 相对定位
相对定位适合于子部件具有固定大小的情形(通过width和height指定子部件大小)。使用这种方式,子部件可以以相对于subcontrol-position和 subcontrol-origin属性定义的原始位置进行移动调整。使用left属性可以把子部件向右移,top属性可以把子部件向左移。例如:
QPushButton::menu-indicator {
image: url(menu_indicator.png);
width: 13px;
height: 13px;
subcontrol-origin: padding;
subcontrol-position: bottom right;
}
当按下按钮时,我们可以把菜单指示器从原来的位置向右下方移动几个像素来模拟按钮按下的状态。
QPushButton::menu-indicator:pressed {
position: relative;
top: 2px;
left: 2px;
}
2. 绝对定位
绝对定位适合于子部件的位置随父部件的变化而变的情形。与前面的例子相同,subcontrol-origin定义了父部件的参考矩形。子部件的矩形区域则可以随后通过相对于这个参考矩形四边的偏移量来定义。
QPushButton::menu-indicator {
border: 2px solid red;
subcontrol-origin: padding;
position: absolute;
top: 2px;
right: 2px;
bottom: 2px;
left: 40px;
}
对于宽度或高度固定的子部件,subcontrol-position被用来说明其在subcontrol-origin指定矩形内的对其方式:
QPushButton::menu-indicator {
image: url(menu_indicator.png);
width: 13px;
subcontrol-origin: padding;
subcontrol-position: bottom right;
position: absolute;
top: 2px;
bottom: 2px;
right: 2px;
}
确定优先顺序
当多个样式规则使用不同的值指定相同的属性时,就会发生冲突。考虑一下样式表
QPushButton#okButton {color: gray}
QPusButton { color: red }
这两个规则都匹配对象名为okButton的QPushButton实例,属性color存在冲突,在这种情况下,QPushButton#okButton通常引用单个对象, QPushButton是类的所有实例,因此认为QPushButton#okButton更特殊,具有更高的优先级。因此,按钮文本的颜色应该为灰色。
同样地,具有伪状态的选择器比未指定伪状态的选择器更特殊。因此,以下样式表指定当鼠标悬停在QPushButton时,QPushButton应该为白色文本。
QPushButton:hover { color: white }
QPushButton { color: red }
下面这种情况则比较棘手:
QPushButton:hover { color: white }
QPushButon:enbaled { color: red }
这种情况下,两者具有相同的特殊性,因此,如果在启用按钮时将鼠标悬停在按钮上,则第二条规则优先。如果在这种情况下我们希望文本为白色,则可以对规则重新排序,如下所示:
QPushButon:enbaled { color: red }
QPushButton:hover { color: white }
或者,我们可以使第一个规则更特殊一些:
QPushButton:hover:enabled { color: white }
QPushButton { color: red }
在类型选择器中,还有一个与上述情况相似的情形,例如:
QPushButton { color: red }
QAbstractButton { color: gray }
这两个样式都适用于QPushButton(因为QPushButton继承自QAbstractButton),并且color属性存在冲突。由于QPushButton继承自QAbstractButton,因此可能会假设QPushButton比QAbstractButton更特殊一些。但是,对于样式表计算,所有类型选择器都具有相同的特殊性,并且最后出现的规则优先。换句话说,对于所有QAbstractButton,包括QPushButton,将color设置gray为。如果我们确实希望QPushButton带有红色文本,则需要对规则进行重新排序。
根据上面的分析,我们可以对样式优先级的规则总结如下:
1. 特殊实例的优先级大于类的优先级;
2. 具有伪状态的选择器的优先级大于没有伪状态的选择器;
3. 所有的类型选择器都具有相同的特效性(子类和父类的特殊性一样);
4. 具有相同的特殊性时,则排在后面的规则优先。
级联
QApplication,父窗口部件,子窗口部件都可以设置样式表,这样会出现一种级联现象,通过合并部件的祖先(父部件,祖父部件)的样式表以及在QApplication上设置的样式表,产生一个具体部件对象的最终样式表。
当发生冲突时,部件自身的样式表优先级最高,其实是父部件,祖父部件...,最后是QApplication的样式表。
例如使用QApplication设置样式表:
qApp.setStyleSheet('QPushButton { color: white }')
然后又使用具体QPushButton对象设置样式表:
myPushButton('color: blue')
最终myPushButton上的文本颜色是蓝色(blue)的。
继承
在经典CSS中,当未明确设置项目的字体和颜色时,它将自动从父项继承。默认情况下,使用Qt样式表时,部件并不能自动继承其父控件的字体和颜色设置。例如,考虑QGroupBox内的QPushButton:
qApp.setStyleSheet('QGroupBox { color: red; }')
在QGroupBox内的QPushButton并不继承父项QGroupBox的颜色,而是使用系统颜色,如果要在QGroupBox及其子级上设置颜色,可以按如下方式设置:
qApp.setStyleSheet('QGroupBox, QGroupBox * { color: red;}')
相反, 使用QWidget.setFont()和QWidget.setPalette()设置字体和调色板会传播到子窗口的部件上。如果希望字体和调色板传播到子窗口部件上,则可以设置Qt. AA_UseStyleSheetPropagationInWidgetStyles标志,如:
QCoreApplication.setAttribute(Qt. AA_UseStyleSheetPropagationInWidgetStyles, True)
启用部件样式的字体和调色板传播后,通过Qt样式表进行的字体和调色板更改将表现为用户手动调用了所有QWidget.setFont()和QWidget.setPalette()方法。
简单样例
下面简单样例代码演示了如何在程序中使用QSS,建议修改变量qssStyle的值,测试各种情况。完整代码如下:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout,
QLabel, QPushButton, QCheckBox, QSpinBox, QComboBox)
class QSSBaseDemo(QWidget):
def __init__(self, parent=None):
super(QSSBaseDemo, self).__init__(parent)
# 设置窗口标题
self.setWindowTitle('实战PyQt5: QSS 基础语法演示')
# 设置窗口大小
self.resize(400, 200)
self.initUi()
#QSS控制部件外观
qssStyle = '''
QPushButton{background-color:green}
QCheckBox, QComboBox, QSpinBox {
color: red;
background-color: white;
font: bold;
}
'''
#加载设置好的样式
self.setStyleSheet(qssStyle)
def initUi(self):
layout = QGridLayout()
layout.setSpacing(10)
layout.addWidget(QLabel('标签1'), 0, 0)
layout.addWidget(QLabel('标签2'), 0, 1)
layout.addWidget(QPushButton('按钮1'), 1, 0)
layout.addWidget(QPushButton('按钮2'), 1, 1)
layout.addWidget(QCheckBox('复选框1'), 2, 0)
layout.addWidget(QCheckBox('复选框2'), 2, 1)
layout.addWidget(QSpinBox(), 3, 0, 1, 2)
items = ['选项1', '选项2', '选项3']
comboBox1 = QComboBox()
comboBox1.addItems(items)
layout.addWidget(comboBox1, 4, 0)
layout.setRowStretch(5, 1)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QSSBaseDemo()
window.show()
sys.exit(app.exec())
运行结果如下图:
一个简单的使用QSS例子
本文知识点
- QSS样式表的基本形式
- QSS选择器
- 伪状态
- 子部件的样式表设置
- 样式的优先顺序
- 样式级联规则
- 样式属性的继承方式
- 使用setStyleSheet()设置样式