在UI开发中经常经常需要做美化,大多商业软件还需要专业的美工支持。Qt作为UI开发的利器,对美化这块支持也是比较友好的。本篇要总结的样式表就是Qt的一种强大的机制,它使界面的表现与界面的元素分离,使得设计皮肤与界面控件分离的软件成为可能。
Qt样式表是允许用户定制widgets组件外观的强大机制,此外,子类化QStyle也可以定制widgets组件外观。QT样式表的概念、术语、语法很大程度上受到了HTML 级联样式表(CSS)的影响。
样式表是文本规范,可以使用QApplication :: setStyleSheet()在整个应用程序上设置,也可以使用QWidget::setStyleSheet()在特定的widget(及其子控件)上设置。如果在不同级别上设置了多个样式表,则Qt将从所有已设置的样式表中得出有效样式表,这称为级联。
例如,以下样式表指定所有QLineEdit都应使用黄色作为其背景色,而所有QCheckBox都应使用红色作为文本色:
QLineEdit { background: yellow }
QCheckBox { color: red }
对于这种定制,样式表比QPalette强大得多。例如,可能很想将QPalette::Button设置为红色,以使QPushButton显示为红色按钮。但是,这不能保证适用于所有样式,因为样式受不同平台的限制,并且(在Windows XP和macOS上)受本机主题引擎的限制。
样式表使您可以执行在仅使用QPalette情况下难以或不可能执行的各种定制。如果您想将黄色背景用作必填字段,将红色文本用于可能具有破坏性的按钮或精美的复选框,则可以使用样式表。
样式表被应用在当前widget样式之上,这意味着您的应用程序看起来将尽可能地原生,但是将考虑任何样式表约束。与QPalette不同,样式表可保证:如果将QPushButton的背景色设置为红色,则可以确保在所有平台上,该按钮在所有样式中均具有红色背景。此外,Qt Designer提供了样式表集成,使您可以轻松地查看不同widget样式的样式表的效果。
此外,样式表可用于为您的应用程序提供独特的外观,而不必继承QStyle。下面的例子显示了两个不同的stylesheet样式风格。
Qt样式表的术语和句法规则与HTML CSS几乎相同。
样式表由样式规则序列组成。样式规则由选择器和声明组成。选择器指定了那些组件受规则影响,声明指定了组件设置了哪些属性。例如:
QPushButton { color: red }
在上述样式规则中,QPushButton是选择器,{ color: red }是声明。该规则指定QPushButton及其子类(例如MyPushButton)应使用红色作为其前景色。
Qt的样式表通常是不区分大小写(即,color,Color,COLOR,和cOloR指的是相同属性)。是类名,对象名和Qt属性名例外,它们区分大小写。
可以为同一声明指定多个选择器,使用逗号(,)分隔选择器。例如,规则
QPushButton, QLineEdit, QComboBox { color: red }
等效于以下三个规则的序列:
QPushButton { color: red }
QLineEdit { color: red }
QComboBox { color: red }
样式规则的声明部分是一个“属性:值”对的链表。声明部分在{}内,不同的属性:值”对使用分号分隔。例如:property: value{}
QPushButton { color: red; background-color: white }
到目前为止,所有示例都使用了最简单的选择器类型,即类型选择器。Qt样式表支持CSS2中定义的所有选择器。下表总结了最有用的选择器类型。
选择器 |
例 |
说明 |
通用选择器 |
* |
匹配所有小部件。 |
类型选择器 |
QPushButton |
匹配QPushButton及其子类的实例。 |
属性选择器 |
QPushButton[flat="false"] |
匹配非flat的QPushButton实例。 |
类选择器 |
.QPushButton |
匹配QPushButton实例,但不匹配其子类的实例。等同于*[class~="QPushButton"]。 |
ID 选择器 |
QPushButton#okButton |
匹配对象为okButton的所有QPushButton实例。 |
后代选择器 |
QDialog QPushButton |
匹配所有QPushButton实例,它们是QDialog后代(子代,孙代等)。 |
儿童选择器 |
QDialog > QPushButton |
匹配所有QPushButton实例,这些实例是QDialog直接子代。 |
对于样式复杂的widgets,必须访问该widget的子控件,例如QComboBox的下拉按钮或QSpinBox的向上和向下箭头。选择器可能包含子控件,这些子控件可以将规则的应用限制到特定的窗口小部件子控件。例如:
QComboBox::drop-down { image: url(dropdown.png) }
上面的规则为所有QComboBox的下拉按钮设置样式。子控件选择器始终相对于另一个元素(参考元素)定位。该参考元素可以是widget或另一个widget。例如,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
{
image: url(down_arrow.png);
}
QComboBox::down-arrow:pressed
{
position: relative;
top: 1px; left: 1px;
}
绝对定位方案(position:absolute)允许相对于参考元素更改子控件的位置和大小。
放置后,它们将与widget件一样可以使用box模型设置样式。
注意:对于复杂的widget,例如QComboBox和QScrollBar,如果自定义一个属性或子控件,则所有其他属性或子控件也必须被自定义。
选择器可能包含伪状态,这些伪状态表示基于widget的状态来限制规则的应用。伪状态出现在选择器的末尾,中间有一个冒号(:)。例如,当鼠标悬停在QPushButton上时,以下规则适用:
QPushButton:hover { color: white }
可以使用感叹号运算符否定伪状态。例如,当鼠标没有悬停在QRadioButton上时,以下规则适用:
QRadioButton:!hover { color: red }
伪状态可以被链接,在这种情况下,逻辑与是隐含的。例如,以下规则适用于鼠标悬停在选中的QCheckBox上方的情况:
QCheckBox:hover:checked { color: white }
否定的伪状态可能会出现在伪状态链中。例如,当鼠标悬停在未按下的QPushButton上时,将应用以下规则:
QPushButton:hover:!pressed { color: blue; }
如果需要,可以使用逗号运算符表示逻辑或:
QCheckBox:hover, QCheckBox:checked { color: white }
伪状态可以与子控件选择器组合出现。例如:
QComboBox::drop-down:hover { image: url(dropdown_bright.png) }
当多个样式规则使用不同的值指定相同的属性时,就会发生冲突。考虑以下样式表:
QPushButton#okButton { color: gray }
QPushButton { color: red }
这两个规则都匹配被调用的QPushButton实例okButton并且该color属性存在冲突。要解决此冲突,我们必须考虑选择器的特殊性。在上面的示例中,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,包括QPushButton,都将color设置gray为。如果我们确实希望QPushButton带有红色文本,则可以随时对规则进行重新排序。
可以在QApplication,父窗口widgets和子窗口widgets上设置样式表。通过合并在widgets祖先(父母,祖父母等)上设置的样式表以及在QApplication上设置的任何样式表,可以获取任意widgets的有效样式表。
当发生冲突时,无论冲突规则的特性如何,始终要优先使用widgets自身的样式表,而不是任何继承的样式表。同样,父窗口widgets的样式表优先于祖父母的样式表等。
这样的结果之一是,在窗口widgets上设置样式规则会自动赋予它优先于祖先窗口widgets的样式表或QApplication样式表中指定的其他规则的优先级。考虑以下示例。首先,我们在QApplication上设置样式表:
qApp->setStyleSheet("QPushButton { color: white }");
然后,在QPushButton对象上设置样式表:
myPushButton->setStyleSheet("* { color: blue }");
QPushButton的样式表使得QPushButton(以及任何子部件)的文字为蓝色,尽管应用程序范围内的样式表提供的更具体规则集。
如果我们写了如下的语句,结果将是相同的。但如果QPushButton有子组件,样式表不会对子组件有效果。
myPushButton->setStyleSheet("color: blue");
样式表级联是一个复杂的主题。有关详细信息,请参阅CSS2规范。
在经典CSS中,当未显式设置项目的字体和颜色时,它将自动从父项继承。默认情况下,使用Qt样式表时,widget并不能自动继承其父控件的字体和颜色设置。
例如,考虑QGroupBox内的QPushButton:
qApp->setStyleSheet("QGroupBox { color: red; } ");
该QPushButton没有一个明确的颜色设置。因此,它没有继承其父QGroupBox的颜色,而是具有系统颜色。如果要在QGroupBox及其子级上设置颜色,可以编写:
qApp->setStyleSheet("QGroupBox, QGroupBox * { color: red; }");
相反,使用QWidget :: setFont()和QWidget :: setPalette()设置字体和调色板会传播到子窗口小部件。如果您希望字体和调色板传播到子窗口,则可以设置Qt :: AA_UseStyleSheetPropagationInWidgetStyles标志,如下所示:
用法:
QCoreApplication::setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true);
启用小部件样式的字体和调色板传播后,通过Qt样式表进行的字体和调色板更改将表现为用户手动调用了所有控件上相应的QWidget :: setPalette()和QWidget :: setFont()方法。
类型选择器特殊类型的组件的样式定制。例如:
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; }");
从4.3及更高版本开始,可以使用qproperty-
MyLabel { qproperty-pixmap: url(pixmap.png); }
MyGroupBox { qproperty-titleColor: rgb(100, 200, 100); }
QPushButton { qproperty-iconSize: 20px 20px; }
如果属性引用了Q_ENUMS声明的枚举,应该通过名字引用常量值,而不是数字。
Qt Designer {Qt Designer}是预览样式表的出色工具。您可以在Designer中的任何窗口widgets上单击鼠标右键,然后选择“ 更改styleSheet ...”以设置样式表。
在Qt 4.2和更高版本中,Qt Designer还包括样式表语法突出显示器和验证器。验证器在“ 编辑样式表”对话框的左下方指示语法是否有效。
当使用样式表时,每个组件会被当作有四个同心矩形:空白矩形、边界矩形、衬底矩形、内容矩形的盒子。盒子模型对此进行了更详细的描述。
四个同心矩形在概念上如下所示:
margin, border-width,padding属性默认都是0。此时四个矩形完全相同。
可以使用background-image属性指定组件的背景。默认,background-image只会在边界矩形内被绘制,使用background-clip属性可以修改。使用background-repeat属性和background-origin属性来控制背景图片的重复和来源。
background-image属性不会缩放组件的大小。为了提供随着组件大小缩放的皮肤或背景,必须使用border-image属性。由于border-image属性提供了一个可选择的背景,当指定border-image属性时,不会要求指定background-image属性。当background-image属性和border-image属性都被指定时,border-image属性会被绘制在background-image属性之上。
此外,image属性可以用于在border-image属性上绘制一幅图像。当组件的大小与image的大小不匹配时,指定的image不会伸缩,对齐方式可以使用image-position属性设置。与background-image属性和border-image属性不同,image属性可以指定SVG,使image根据组件的大小自动缩放。
渲染规则的步骤如下:
A、为整个渲染操作设置clip(border-radius)
B、绘制背景(background-image)
C、绘制边界(border-image,border)
D、绘制覆盖图像(image)
一个组件可以看作一颗子控件树。例如QComboBox绘制下拉按钮子控件,下拉按钮子控件绘制了向下箭头子控件。
子控件享有父子关系。QComboBox中,向下箭头的父亲是下拉按钮,下拉按钮的父亲的QComboBox组件本身。子控件可以使用subcontrol-position熟悉和subcontrol-origin属性定位在父组件内。
一旦定位,子控件就可以使用盒子模型定制样式。
注意,像QComboBox和QScrollBar这样复杂的组件,如果有一个属性或是子控件选择器被定制,所有其他的属性或是子控件选择器也要必须被定制。
使用样式表可以是上述例子中的程序语言设置,也可以通过Qt Designer添加。
qApp->setStyleSheet("QLineEdit { background-color: yellow }");
*[mandatoryField="true"] { background-color: yellow }
QLineEdit *nameEdit = new QLineEdit(this);
nameEdit->setProperty("mandatoryField", true);
也可以通过读取qss文本文件来批量设置:
QFile file(styleSheetFile);
file.open(QFile::ReadOnly);
if (file.isOpen())
{
QString styleSheet = this->styleSheet();
styleSheet += QLatin1String(file.readAll());//读取样式表文件
this->setStyleSheet(styleSheet);//把文件内容传参
file.close();
}
如果以文本文件的方式编写了样式表文件,加载qss文件时,需要指定文件路径(一般是相对路径),如果把qss文件放在磁盘上,文件暴露在用户眼皮底下,可能会被用户随意修改,那么我们可以用Qt的资源系统把它编译到可执行文件中去,这在我的Qt资源系统中有具体介绍。
我这里给出几个我用过的例子供参考。
其他脚本:
一个红色ok按钮:
QPushButton#okButton
{
background-color: red;
border-style: outset;
border-width: 2px;
border-radius: 10px;
border-color: beige;
font: bold 14px;
min-width: 10em;
padding: 6px;
}
QPushButton#okButton:pressed
{
background-color: rgb(224, 0, 0);
border-style: inset;
}
一个带复杂状态切换的checkbox
QCheckBox {
spacing: 5px;
}
QCheckBox::indicator {
width: 13px;
height: 13px;
}
QCheckBox::indicator:unchecked {
image: url(:/images/checkbox_unchecked.png);
}
QCheckBox::indicator:unchecked:hover {
image: url(:/images/checkbox_unchecked_hover.png);
}
QCheckBox::indicator:unchecked:pressed {
image: url(:/images/checkbox_unchecked_pressed.png);
}
QCheckBox::indicator:checked {
image: url(:/images/checkbox_checked.png);
}
QCheckBox::indicator:checked:hover {
image: url(:/images/checkbox_checked_hover.png);
}
QCheckBox::indicator:checked:pressed {
image: url(:/images/checkbox_checked_pressed.png);
}
QCheckBox::indicator:indeterminate:hover {
image: url(:/images/checkbox_indeterminate_hover.png);
}
QCheckBox::indicator:indeterminate:pressed {
image: url(:/images/checkbox_indeterminate_pressed.png);
}
一个带复杂状态的combobox
QComboBox {
border: 1px solid gray;
border-radius: 3px;
padding: 1px 18px 1px 3px;
min-width: 6em;
}
QComboBox:editable {
background: white;
}
QComboBox:!editable, QComboBox::drop-down:editable {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E1E1E1, stop: 0.4 #DDDDDD,
stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3);
}
/* QComboBox gets the "on" state when the popup is open */
QComboBox:!editable:on, QComboBox::drop-down:editable:on {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #D3D3D3, stop: 0.4 #D8D8D8,
stop: 0.5 #DDDDDD, stop: 1.0 #E1E1E1);
}
QComboBox:on { /* shift the text when the popup opens */
padding-top: 3px;
padding-left: 4px;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 15px;
border-left-width: 1px;
border-left-color: darkgray;
border-left-style: solid; /* just a single line */
border-top-right-radius: 3px; /* same radius as the QComboBox */
border-bottom-right-radius: 3px;
}
QComboBox::down-arrow {
image: url(/usr/share/icons/crystalsvg/16x16/actions/1downarrow.png);
}
QComboBox::down-arrow:on { /* shift the arrow when popup is open */
top: 1px;
left: 1px;
}