QSS详解
简述
QSS(Qt Style Sheets)是Qt样式表,与制作前端web中使用CSS来美化网页一样, QSS为Qt提供属性、伪状态、子控件等机制来自定义控件外观。对于设计Qt应用程序,QSS是必须掌握的,因为它能让你付出最少的代价,美化应用程序。而不用去继承QWidget,做很多复杂的工作(当然我们不是说这种方式不重要,后面会介绍这种方式,并且以实现ribbon为目标)。另外,QtQuick和QGraphicsView能够定制更好的用户体验,后面再一点点体验和分享给大家。
QSS 编辑器
工欲善其事,必先利其器,一款好的编辑器有利于快速编写所需样式。
-
Qt Creator
Qt Creator默认是不会高亮qss内容,这个只需要配置一下:- 进入:工具 -> 选项 -> 环境 -> MIME 类型。
- 在【已注册的MIME类型】处输入“text/css”可以快速定位,然后在【详情】中的“模式”处添加
*.qss
,即将原来的“模式”改为:*.css;*.CSSL;*.qss
。
【注意】中间用分号(;)分隔
-
QSS Editor
QSS Editor可以预览,没有语法提示。 -
Kineticwing IDE
KiWi 是一款智能、轻便、便携的 IDE,可以更快捷、更轻松的进行 WEB 开发。支持 HTML、CSS、QSS、SASS、JavaScript、PHP、XML、ASP、Perl 等。
对 QSS 的支持,主要包含以下功能:
-
代码突出显示(语法着色)
-
括号高亮显示(当光标位于包含字符的开始和结束之间时,编辑器会成对突出显示大括号)
-
折叠代码段
-
保存状态标记(绿色是保存状态,红色是未保存状态)
-
智能自动完成
缺点:无法实时预览。
QSS 语法
QSS 包含了一个样式规则序列,术语和语法规则几乎和CSS相同。
样式规则
-
QSS包含了一个样式规则序列,一个样式规则由一个选择器和声明组成,选择器指定哪些部件由规则影响,声明指定哪些属性应该在部件上进行设置。例如:
QPushButton { color: red }
上面的例子中QPushButton是选择器,{ color: red }是声明,该规则指定QPushButton及其子类(例如:MyPushButton)应使用红色作为前景色。 -
QSS通常不区分大小写(即:color、Color、COLOR、cOloR指同一属性),唯一例外就是类名(class names)、对象名(object names)、属性名(property names)区分大小写。
-
几个选择器可以指定相同的声明,使用逗号(,)来分隔选择器。例如:
QPushButton, QLineEdit, QComboBox { color: red }
等价于QPushButton { color: red } QLineEdit { color: red } QComboBox { color: red }
-
声明部分的规则是一个属性值对(property: value)列表,包含在花括号中,以分号分隔。例如:
QPushButton { color: red; background-color: white }
更多声明规则,可以查阅Qt文档,Qt Style Sheets Reference中List of Properties部分,
或是直接查阅在线文档Qt Style Sheets Reference -
选择器类型
选择器 | 例子 | 详细描述 |
---|---|---|
通用选择器 | * | 匹配所有控件 |
类型选择器 | QPushButton | 匹配QPushButton 实例和其子类 |
属性选择器 | QPushButton[flat="false" ] | 匹配QPushButton中flat属性为false的实例。可以用此选择器来测试任何支持QVariant::toString()的属性,此外,支持特殊的类属性、类名称。此选择器也可以用来测试动态属性(参考助手:Qt Style Sheets Examples中Customizing Using Dynamic Properties部分)。还可以使用~=替换=,测试QStringList类型的属性是否包含给定的QString。 警告:如果Qt属性值在设置样式之后更改,那么可能需要强制重新计算样式。实现的一个方法是取消样式,然后重新设置一遍。 |
类选择器 | .QPushButton | 匹配QPushButton 实例,但是不包括其其子类。其等价于 *[class~="QPushButton"] |
ID选择器 | QPushButton#okButton | 匹配所有对象名为“okButton”的QPushButton 实例 |
后代选择器 | QDialog QPushButton | 匹配所有属于QDialog后代(子部件,孙子部件…)的QPushButton 实例 |
子选择器 | QDialog > QPushButton | 匹配所有属于QDialog直接子部件的QPushButton 实例 |
- 子控件
- 对于样式复杂的控件,需要访问其子控件,例如
QComboBox
的下拉按钮,或QSpinBox
递增邪恶向上箭头和递减的向下箭头。选择器应该包含子控件,让他可以限制指定控件的副控件的应用规则。例如:
QComboBox::drop-down { image: url(dropdown.png) }
上述规则指定了QComboBoxe下拉按钮样式,虽然双冒号(::)语法让人想起CSS3伪元素,但Qt子控件从概念上讲有不同的级联语义 - 子控件定位总是相对于另一个参考元素。这个参考元素可能是小部件或其它子控件。例如:QComboBox的::drop-down放置,默认的放置在QComboBox区域的右上角,::drop-down放置,默认的在::drop-down子控件的中央,参考助手:Qt Style Sheets Reference中List of Stylable Widgets部分。
可以使用subcontrol-origin改变子控件原始的默认位置,
QComboBox {
margin-right: 20px;
}
QComboBox::drop-down {
subcontrol-origin: margin;
}
下拉框在边框内的对齐方式,可以通过subcontrol-position属性改变。
高度和宽度属性可以控制子控件的尺寸,
【注意】设置图片的大小会隐式的改变子控件的大小,这个比较重要,你试着用32px的icon作为控件的图标,工作在1920x1080的屏幕上。 当换成4k屏(4096×2160)时,你的所有控件都变小,缩成一堆。解决方案(根据像素换成大图标,没有去实践,手上没有4K屏)…这个问题找到准确解决方案在补上。
相对位置方案 (position : relative
), 即允许子控件相对于其初始位置做偏置。例如: QComboBox
下拉按钮被按下时,我们可以使其内部的下拉箭头在做一些偏置,达到被按下的效果。具体实现如下:
QComboBox::down-arrow {
image: url(down_arrow.png);
}
QComboBox::down-arrow:pressed {
position: relative;
top: 1px; left: 1px;
}
绝对位置方案 (position : absolute
),允许相对于参考元素改变子控件位置和大小。
一旦位置被设定,这些子控件将被和普通部件(widgets)视为相同,并且可以使用盒模型样式(参考助手:Customizing Qt Widgets Using Style Sheets中The Box Model部分)。
**注意:**对于复杂的部件,如:QComboBox和QScrollBar,如果一个属性或子控件被定制,所有其它属性或子控件必须被定制好。
- 伪状态(Pseudo-States)
什么是伪状态,比如:当鼠标经过或停留在按钮时,按钮颜色发生改变。
-
选择器可以包含伪状态,表示基于控件的状态限制应用程序的规则。伪状态出现在选择器后面,用冒号(:)关联。例如下面的应用规则,鼠标划过按钮:
QPushButton:hover { color: white }
-
伪状态可以用取反声明符(!)表示否定,例如下面的应用规则,鼠标没有经过
QRadioButton
上方:
QRadioButton:!hover { color: red }
-
伪状态可以连接使用,用冒号(:)将所有伪状态连起来,相当于逻辑与(AND)操作符。例如:鼠标经过一个被选中的按钮。
QCheckBox:hover:checked { color: white }
-
伪状态链中可以有取反(!)的伪状态,例如:鼠标经过一个没有被按下的按钮
QPushButton:hover:!pressed { color: blue; }
-
如果需要,逻辑或(OR)可以被表示成逗号(,)
QCheckBox:hover, QCheckBox:checked { color: white }
-
伪状态可以用在子控件中,例如:
QComboBox::drop-down:hover { image: url(dropdown_bright.png) }
更加详细可以参考助手:Qt Style Sheets Reference中List of Pseudo-States部分。
- 消除冲突
- 几种风格规则用不同值来指定相同属性会引起冲突,考虑下面的样式规则:
QPushButton#okButton { color: gray }
QPushButton { color: red }
两个规则都匹配到对象名为okButton
的 QPushButton
实例,那么color
这个属性有冲突,所以必须考虑两个选择器的特殊性。对象名为okButton
的 QPushButton
实例是比QPushButton
特殊,因为他通常指的一个对象,而不是所有QPushButton
对象。
- 同样的,有伪状态选择器则比没有指定伪状态的选择器特殊,因此,下面的例子中, 当鼠标经过
QPushButton
时,使用的应该时白色的文本
QPushButton:hover { color: white }
QPushButton { color: red }
- 当然也有更加棘手的情况,如果特殊性相同,那最后声明的选择器优先。
如下:QPushButton
文本颜色为红色
QPushButton:hover { color: white }
QPushButton:enabled { color: red }
如果要设置QPushButton
文本颜色为白色,只需要将上面白色的属性样式放到最后,如下所示:
QPushButton:enabled { color: red }
QPushButton:hover { color: white }
或者使QPushButton
前景为白色属性的样式更加特殊,如下所示
#添加了enabled的伪状态
QPushButton:hover:enabled { color: white }
QPushButton:enabled { color: red }
- 类似的问题出现在类型选择器,如下所示,两个类型选择器为继承关系:
QPushButton { color: red }
QAbstractButton { color: gray }
上面的两条样式规则都会作用于QPushButton
(自从QPushButton
继承 QAbstractButton
), 那这里就有颜色属性的争执。 因为QPushButton
继承 QAbstractButton
,则认为QPushButton
比QAbstractButton
更加具有特殊性。
当然,对于样式的计算,所有拥有相同特殊性的类型的选择器,则顺序在最后的样式优先。或者说,所有QAbstractButtons
的前景颜色都设置为灰色,包括QPushButtons
。如果我们真正想 QPushButtons
设置为红色的前景,我们可以把描述样式的规则重新排序。
- 样式规则特殊性小结
为了确定一个规则的特殊性,QSS遵循CSS2规范:
- 选择器ID属性的数量 (=a)
- 选择器其他属性和伪状态类性数量 (=b)
- 选择器元素名的数量 (=c)
- 忽略伪元素 (例如:子控件)
把a-b-c连接在一起(一个拥有很大基数的数字系统)决定特殊性。
如下一些例子:
* {} /* a=0 b=0 c=0 -> specificity = 0 */
LI {} /* a=0 b=0 c=1 -> specificity = 1 */
UL LI {} /* a=0 b=0 c=2 -> specificity = 2 */
UL OL+LIa {} /* a=0 b=0 c=3 -> specificity = 3 */
H1 + *[REL=up]{} /* a=0 b=1 c=1 -> specificity = 11 */
UL OL LI.red {} /* a=0 b=1 c=3 -> specificity = 13 */
LI.red.level {} /* a=0 b=2 c=1 -> specificity = 21 */
#x34y {} /* a=1 b=0 c=0 -> specificity = 100 */
-
级联
样式可以说被设置在QApplication
, 在父控件,子控件。任意控件的有效样式可以通过合并其祖先的(父级,祖父级等)以及QApplication
上设置的样式获得。
考虑这样的一个结果,自动设置一个控件样式规则,它的优先级高于其祖先或者QApplication
的样式规则规则。 如下,为QApplication
设置样式表
qApp->setStyleSheet("QPushButton { color: white }");
然后我们为QPushButton
对象设置样式,如下所示:
myPushButton->setStyleSheet("* { color: blue }");
QPushButton
的样式就会迫使QPushButton
(以及它的子控件)前景文本为蓝色。尽管应用程序范围内提供了更多特殊的规则,但QPushButton
(以及它的子控件)并不会使用。
如果按照下面这种方式写,其结果是相同的:
myPushButton->setStyleSheet("color: blue");
除非QPushButton
有子控件,那么这个样式将不会影响到。不过一般QPushButton
不会有子控件。
样式极联是一个复杂的主题。请参考CSS2 Specification,请注意,目前Qt没有实现。 -
继承
在经典的CSS中,当字体和颜色等项目没有显式设置,它会自动继承父级控件的样式。默认情况下,在QSS中,一个控件是不会自动继承它父级控件的字体和颜色的设置。
例如:QGroupBox
里面的子控件QPushButton
:
qApp->setStyleSheet("QGroupBox { color: red; } ");
QPushButton
没有显式设置颜色, 因此,QPushButton
设置为系统的颜色,而没有继承它的父级控件颜色。如果我们想设置QGroupBox
及其子级控件的样式,我们可以这么写:
qApp->setStyleSheet("QGroupBox, QGroupBox * { color: red; }");
于此相比,可以使用QWidget::setFont()
和QWidget::setPalette()
设置字体和调色板用到子控件中。
如果你想字体和调色板可以传播到子控件,可以设置这个标志:Qt::AA_UseStyleSheetPropagationInWidgetStyles
使用方法:
QCoreApplication::setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true);
当控件风格中字体和调色板的传播使能,字体和调色板通过QSS所做的更改将会执行,即使用户手动调用对应QWidget::setPalette()
和QWidget::setFont()
的方法在所有QWidgets 所针对的样式目标。 -
控件在NameSpace中
类型选择器可以被用于特定的控件风格。例如:
class MyPushButton : public QPushButton {
// ...
}
// ...
qApp->setStyleSheet("MyPushButton { background: yellow; }");
Qt 样式使用控件的类名(QObject::className()
)来决定是否应用类型选择器。当自定义的控件在命名空间中(namespaces), 类名(QObject::className()
)返回<namespace>::<classname>
。这个命名方式于子控件的样式规则命名方式有冲突。为了克服这个问题,当空间选择器在命名空间内时,我们使用“–”替代“::”。如下所示:
namespace ns {
class MyPushButton : public QPushButton {
// ...
}
}
// ...
qApp->setStyleSheet("ns--MyPushButton { background: yellow; }");
子控件的样式规则,如果命名空间的表示方式不做更改,那就会发生冲突:
QComboBox::down-arrow {
image: url(down_arrow.png);
}
- 设置
QObject
的属性
在Qt4.3以后,任何可以设计为Q_PROPERTY
都可以使用qproperty-<property name>
的语法。如下例子:MyLabel { qproperty-pixmap: url(pixmap.png); } MyGroupBox { qproperty-titleColor: rgb(100, 200, 100); } QPushButton { qproperty-iconSize: 20px 20px; }
如果属性参考 Q_ENUMS
的枚举声明,我们应该参考它常量的名字,而不是数值。