QT开发(六十四)——QT样式表
本文主要翻译自QT官方文档Qt Style Sheets 。
一、QT样式表简介
1、QT样式表简介
QSS的主要功能是使界面的表现与界面的元素分离,使得设计皮肤与界面控件分离的软件成为可能。
QT样式表是允许用户定制widgets组件外观的强大机制,此外,子类化QStyle也可以定制widgets组件外观。QT样式表的概念、术语、语法很大程度上受到了CSS层叠样式表的影响。
样式表是使用QApplication::setStyleSheet()设置在应用程序或是使用QWidget::setStyleSheet()设置在具体组件及其子对象的文字说明。
例如,以下样式表指明QLineEdit使用×××作为背景色,QCheckBox使用红色作为文本颜色。
QLineEdit { background: yellow }
QCheckBox { color: red }
对于这种定制,样式表比QPalette更强大。例如,要获取一个红色的按钮,可以设置QPushButton的QPalette::Button角色为红色。然而,这并不保证对所有的样式有效,因为样式的构造者会被不同平台的原则和本地的主题引擎所限制。
样式表可以实现那些很难或是不可能使用QPalette实现的所有定制。
如果想要某些强制字段为×××背景,按钮为红色文字,或是绚丽的复选框,样式表可以完成。
2、QT样式表编程
A、在代码中直接设置QSS
QPushButton *button = new QPushButton(this);
button->setText("hello");
button->setStyleSheet("QPushButton{background-color:red; color:blue}");
B、通过文件设置QSS
将QSS代码写入.qss文件
将.qss文件加入资源文件qrc.qrc中
在代码中读取QSS文件设置组件QSS
QPushButton *button = new QPushButton(this);
button->setText("hello");
QFile file(":/qss/dev.qss");
file.open(QIODevice::ReadOnly);
QString stylesheet = QLatin1String(file.readAll());
button->setStyleSheet(stylesheet);
file.close();
二、QT样式表语法
QT样式表的术语和语法规则与HTML CSS很相似。
1、样式规则
样式表由样式规则序列组成。样式规则由选择器和声明组成。选择器指定了那些组件受规则影响,声明指定了组件设置了哪些属性。例如:
QPushButton { color: red }
以上样式规则中,QPushButton是选择器,{ color: red }是声明。样式规则指定了QPushButton及其子类应使用红色作为前景色。
QT样式表大小写不敏感,除了类名、对象名、QT属性名是大小写敏感的。
多个选择器可以指定同一个声明,使用逗号分隔选择器。例如以下规则:
QPushButton, QLineEdit, QComboBox { color: red }
等效于三个规则:
QPushButton { color: red }
QLineEdit { color: red }
QComboBox { color: red }
样式规则的声明部分是一个“属性:值”对的链表。声明部分在{}内,不同的属性:值”对使用分号分隔。例如:
QPushButton { color: red; background-color: white }
2、选择器的类型
到目前为止所有的例子使用的都是最简单的选择器类型。QT样式表支持CSS2定义的所有选择器。下表总结了最常用的选择器类型。
选择器 |
示例 |
说明 |
通用选择器 |
* |
匹配所有的组件 |
类型选择器 |
QPushButton |
匹配所有QPushButton机器子类的实例 |
属性选择器 |
QPushButton[flat="false"] |
匹配所有QPushButton的属性flat为false的实例。属性分为静态属性和动态属性,静态属性可以通过Q_PROPERTY()来指定,动态属性可以使用setProperty来指定。 |
类选择器 |
.QPushButton |
匹配QPushButton的实例,子类除外 |
ID选择器 |
QPushButton#okButton |
匹配对象名为okButton的所有QPushButton实例
|
后代选择器 |
QDialog QPushButton |
匹配QDialog后代的所有QPushButton实例 |
子选择器 |
QDialog > QPushButton |
匹配QDialog子类对象QPushButton的所有实例 |
3、子控件选择器
对于样式复杂的组件,需要访问组件的子控件,如QComboBox的下拉按钮或QSpinBox的上下箭头。选择器可以包含子控件来对组件的特定子控件应用规则。
QComboBox::drop-down { p_w_picpath: url(dropdown.png) }
以上规则会对所有QComboBox的下拉框应用样式规则。尽管双冒号::很像CSS3的伪元素,但QT子控件选择器在概念上是不同的,有不同的级联语义。
子控件选择器通常根据另一个参考元素进行定位。参考元素可以是组件或是另一个子控件选择器。例如,QComboBox的::drop-down默认放置在QComboBox衬底矩形的右上角。::drop-down默认放置在::drop-down子控件选择器的内容矩形的中心。
可以使用subcontrol-origin属性改变原点矩形。例如,如果想要将drop-down放置在边界矩形而不是默认的衬底矩形,可以指定:
QComboBox {
margin-right: 20px;
}
QComboBox::drop-down {
subcontrol-origin: margin;
}
drop-down在边界矩形内的对齐方式通过subcontrol-position属性改变。宽属性和高属性用于控制子控件选择器的大小。注意,设置一幅图片会隐式地设置子控件选择器的大小。
相对定位方法(position:relative)允许子控件选择器的位置偏离它原来的位置。例如,当QComboBox的下拉按钮按下,想要一种被按下的效果可以通过指定如下实现:
QComboBox::down-arrow {
p_w_picpath: url(down_arrow.png);
}
QComboBox::down-arrow:pressed {
position: relative;
top: 1px; left: 1px;
}
绝对定位方法(position:absolute)允许子控件选择器的位置和大小的改变与参考元素有关。
一旦定位,就可以像组件那样使用盒子模型对其进行造型。
注意,像QComboBox和QScrollBar这样复杂的组件,如果有一个属性或是子控件选择器被定制,所有其他的属性或是子控件选择器也要必须被定制。
常用辅助控制器:
::indicator 单选框、复选框、可选菜单项或可选群组项的指示器
::menu-indicator 按钮的菜单指示器
::item 菜单、菜单栏或状态栏项
::up-button 微调框或滚动条的向下按钮
::down-button 微调框或滚动条的向上按钮
::up-arrow 微调框、滚动条或标题视图的向上按钮
::down-arrow 微调框、滚动条或标题视图的向下按钮
::drop-down 组合框的下拉箭头
::title 群组框的标题
4、伪选择器
选择器可能包含限制基于组件状态的规则应用的伪状态。伪状态出现在选择器的尾部,中间使用分号连接。例如,以下规则用于鼠标悬停在一个QPushButton上:
QPushButton:hover { color: white }
伪状态可以使用叹号取反,例如,以下规则用于鼠标不悬停在QRadioButton:
QRadioButton:!hover { color: red }
伪状态可以以逻辑与的方式连接使用,例如,以下规则应用于鼠标悬停在一个选中的QCheckBox上:
QCheckBox:hover:checked { color: white }
取反伪状态可以出现在伪状态链中。例如,以下规则用于鼠标悬停在一个QPsuButton上,但没有按下:
QPushButton:hover:!pressed { color: blue; }
如果需要,使用逗号可以将伪状态以逻辑或连接。
QCheckBox:hover, QCheckBox:checked { color: white }
伪状态可以结合子控件选择器使用:
QComboBox::drop-down:hover { p_w_picpath: url(dropdown_bright.png) }
常用状态:
:disabled 禁用的窗口部件
:enabled 启用的窗口部件
:focus 窗口部件有输入焦点
:hover 鼠标在窗口部件上悬停
:pressed 鼠标按键点击窗口部件
:checked 按钮已被选中
:unchecked 按钮未被选中
:indeterminate 按钮被部分选中
:open 窗口部件处于打开或扩展的状态
:closed 窗口部件处于关闭或销毁状态
:on 窗口部件的状态是on
:off 窗口部件的状态是on
5、冲突处理
当多个样式规则使用多个值指定同一个属性时会产生冲突。
QPushButton#okButton { color: gray }
QPushButton { color: red }
以上两条规则匹配名为okButton的QPushButton实例,在color属性上有冲突。为了解决冲突,需要考虑选择器的特征。上例中,QPushButton#okButton比QPushButton要更具体,因为QPushButton#okButton指向单个的对象,而不是类的所有实例。
类似,伪选择器比未指定伪状态的选择器更具体。因而,以下样式表指定,当鼠标悬停在QPushButton上时,QPushButton的文本为白色,否则为红色。
QPushButton:hover { color: white }
QPushButton { color: red }
严谨一点应该是:
QPushButton:hover { color: white }
QPushButton:enabled { color: red }
如果鼠标悬停在按钮上,两个选择器有相同的特性,则第二条规则优先。如果想要文本为白色,需要重新排序规则:
QPushButton:enabled { color: red }
QPushButton:hover { color: white }
或者,可以使第一条规则更加具体:
QPushButton:hover:enabled { color: white }
QPushButton:enabled { color: red }
和类型选择器一起使用时,类似的问题也会发生
QPushButton { color: red }
QAbstractButton { color: gray }
以上两条规则用于QPushButton实例(由于QPushButton继承自QAbstractButton),并有color属性的冲突。由于QPushButton继承自QAbstractButton,所以会假设QPushButton比QAbstractButton更具体。然而,对于样式表的计算,所有的类型选择器有相同的特性,最后出现的规则优先。换句话说,所有的QAbstractButton的color属性会被设置为gray,包括QPushButton。如果真的想要QPushButton的文本为red,通常会重新排序规则。
为了确定规则的特性,QT样式表遵循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+LI {} /* 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 */
6、级联
QT样式表可以设置在应用程序、父组件、子组件上。通过合并组件的祖先(父亲、祖父等)可以获取任意组件的有效样式表,以及设置在应用程序上的任何样式表。
冲突发生时,不论冲突规则的特性如何,组件自己的样式表总是优先于任何继承而来的样式表。同样,父组件的样式表优先于祖父组件的样式表。
这样的结果是,在一个组件上设置样式规则会自动获得比祖先组件的样式表或是应用程序的样式表指定的其他规则更高的优先级。例如,首先在应用程序设置样式表
qApp->setStyleSheet("QPushButton { color: white }");
然后,在QPushButton对象设置一个样式表
myPushButton->setStyleSheet("* { color: blue }");
QPushButton的样式表会强制QPushButton(及其任何子组件)显示蓝色文本,尽管应用程序范围的样式表提供了更具体的规则。
下列写法也会得到相同的结果:
myPushButton->setStyleSheet("color: blue");
但如果QPushButton有子组件,样式表不会对子组件有效果。
样式表级联是一个复杂的主题,更详细的内容请参考CSS2规范。QT目前没有实现。
7、继承
在经典的CSS中,当元素的字体和颜色没有显示设置时,会自动从父组件继承。使用QT样式表时,一个组件不会自动继承父组件设置的字体和颜色。例如,一个QGroupBox包含一个QPushButton:
qApp->setStyleSheet("QGroupBox { color: red; } ");
QPushButton并没有显示设置颜色,因此并不是继承父组件QGroupBox的颜色,而是拥有系统的颜色。如果要设置QGroupBox及其子组件的颜色,如下:
qApp->setStyleSheet("QGroupBox, QGroupBox * { color: red; }");
相比之下,使用QWidget::setFont() 和 QWidget::setPalette()为子组件设置字体和画板。
8、C++命名空间内部的组件
类型选择器特殊类型的组件的样式定制。例如:
class MyPushButton : public QPushButton {
// ...
}
// ...
qApp->setStyleSheet("MyPushButton { background: yellow; }");
QT样式表使用组件的QObject::className() 确定何时应选择器。当自定义组件在命名空间内部时,QObject::className()会返回
namespace ns {
class MyPushButton : public QPushButton {
// ...
}
}
// ...
qApp->setStyleSheet("ns--MyPushButton { background: yellow; }");
9、设置QObject属性
从QT4.3开始,任何可被设计的Q_PROPERTY都可以使用qproperty-
MyLabel { qproperty-pixmap: url(pixmap.png); }
MyGroupBox { qproperty-titleColor: rgb(100, 200, 100); }
QPushButton { qproperty-iconSize: 20px 20px; }
如果属性引用了Q_ENUMS声明的枚举,应该通过名字引用常量值,而不是数字。
三、QT设计器中的样式表
Qt Designer是一款预览样式表的优秀工具,右击设计器中的任何组件,选择Change styleSheet...可以设置样式表。
QT4.2开始,Qt Designer包含了一个样式表语法高亮器和验证器。如果语法合法或非法,验证器可以在Edit Style Sheet对话框的左下角指示。
当点击Ok或Apply按钮时,Qt Designer会自动使用新样式表显示组件。
四、使用样式表定制QT组件
当使用样式表时,每个组件会被当作有四个同心矩形:空白矩形、边界矩形、衬底矩形、内容矩形的盒子。
1、盒子模型
四个同心矩形如下所示:
margin, border-width,padding属性默认都是0。此时四个矩形完全相同。
可以使用background-p_w_picpath属性指定组件的背景。默认,background-p_w_picpath只会在边界矩形内被绘制,使用background-clip属性可以修改。使用background-repeat属性和background-origin属性来控制背景图片的重复和来源。
background-p_w_picpath属性不会缩放组件的大小。为了提供随着组件大小缩放的皮肤或背景,必须使用border-p_w_picpath属性。由于border-p_w_picpath属性提供了一个可选择的背景,当指定border-p_w_picpath属性时,不会要求指定background-p_w_picpath属性。当background-p_w_picpath属性和border-p_w_picpath属性都被指定时,border-p_w_picpath属性会被绘制在background-p_w_picpath属性之上。
此外,p_w_picpath属性可以用于在border-p_w_picpath属性上绘制一幅图像。当组件的大小与p_w_picpath的大小不匹配时,指定的p_w_picpath不会伸缩,对齐方式可以使用p_w_picpath-position属性设置。与background-p_w_picpath属性和border-p_w_picpath属性不同,p_w_picpath属性可以指定SVG,使p_w_picpath根据组件的大小自动缩放。
渲染规则的步骤如下:
A、为整个渲染操作设置clip(border-radius)
B、绘制背景(background-p_w_picpath)
C、绘制边界(border-p_w_picpath,border)
D、绘制覆盖图像(p_w_picpath)
2、子控件
一个组件可以看作一颗子控件树。例如QComboBox绘制下拉按钮子控件,下拉按钮子控件绘制了向下箭头子控件。
子控件享有父子关系。QComboBox中,向下箭头的父亲是下拉按钮,下拉按钮的父亲的QComboBox组件本身。子控件可以使用subcontrol-position熟悉和subcontrol-origin属性定位在父组件内。
一旦定位,子控件就可以使用盒子模型定制样式。
注意,像QComboBox和QScrollBar这样复杂的组件,如果有一个属性或是子控件选择器被定制,所有其他的属性或是子控件选择器也要必须被定制。