阡陌 发表于 2023-12-19 00:55:29

初识 QSS



!(https://file.mculoop.com/images/2022/11/qtbanner_1668587316.jpg)

## 简介

CSS( Cascading Style Sheets,层叠样式表)把现在的网页打扮的或花枝招展、或简约朴素。使用 CSS 将结构和表现分离的设计理念启发了 Qt 的样式设计,于是出现了 QSS( Qt Style Sheets ),给界面的开发增添了极大的灵活性。这就好比界面部件( Widget )结构化了人的衣着(帽子、上衣、裙子、鞋子),QSS 则定义了这些衣服的颜色、花纹等样式,每天只需关注换什么样式即可,不用去想要不要穿裙子(当然,你非要穿裤子也是没问题的)。

<!--more-->



## 语法结构

在语法上 QSS 与 CSS 大体上是一致的,由两个主要的部分构成:选择器( selector ),以及一条或多条声明( declaration )。

```css
selector {
declaration1;
declaration2;
...
declarationN
}
```



## 选择器

选择器是我们的 widget,比如 QPushButton、QLineEdit 等,可以使用星号 `*` 来匹配所有的选择器。当界面上有多个 QLineEdit ,而我们却只想改变特定 QLineEdit 的样式时可以使用 `QObject::setObjectName()` 给那个 QLineEdit 命个名字,然后使用 ID 选择器来设置样式表。

```css
QLineEdit#nameEdit { background-color: yellow }
```

当然了,我们也可以直接修改一个部件的样式表,像这样:

```cpp
nameEdit->setStyleSheet("color: blue;"
                        "background-color: yellow;"
                        "selection-color: yellow;"
                        "selection-background-color: blue;");
```

从上边的例子不难看出声明( declaration )的结构应该是这样的:

```css
property: value
```

property 是要设置的样式属性名,value 则是它的值。比如 border、background、margin、padding 等都是属性名。属性和值之间用冒号隔开,两条声明间用分号隔开。声明中的大小写是不敏感的。



## 盒子模型( The Box Model )

怎么理解 padding、border、margin 这些属性呢?

当使用样式表的时候,每个部件( widget )都被当作一个由四个同心矩形组成的盒子来对待。这四个矩形是 the margin rectangle、the border(边框) rectangle、the padding rectangle 和 the content(内容) rectangle 。

!(https://file.mculoop.com/images/2022/11/stylesheet-boxmodel_1668590866.png)

margin、border-width 和 padding 的默认值是 0 ,所以这四个矩形看起来是大小一致的。属性中的 background 是指 border 里边的背景,也就是 padding 矩形区域。盒子的渲染步骤为:

1. 设置整个渲染操作的剪辑区域(如 border-radius 边角半径属性);
2. 绘制背景(如 background-image 属性);
3. 绘制边框(如 border-image, border 属性);
4. 绘制叠加图像(如 image 属性);



## 子控件( Sub-controls )

一些复杂的部件还包含一些子控件,比如 QComboBox(下拉列表框部件)。它含有一个下拉按钮子控件,下拉按钮控件又含有一个下拉箭头子控件。用 `QComboBox::drop-down` 做下拉按钮的选择器,用 `QComboBox::down-arrow` 做下拉箭头的选择器。子控件是以双冒号开头的。

!(https://file.mculoop.com/images/2022/11/QComboBox_191006_1668590872.jpg)



## 伪状态( Pseudo-States )

选择器与伪状态结合起来可以限定部件在某一状态下的样式,伪状态以单个冒号开头。比较常用的比如 `:hover` ,表示鼠标在部件上时候的状态。伪状态支持非运算符, `!:hover` 则表示鼠标不在部件上的时候。既然有非逻辑,那么有没有与和或逻辑呢?

这些可以有,当多个伪状态连着写就表示他们之间是与的关系,下例为对 `QRadioButton::indicator` 选择器各状态的样式定义:

```css
QRadioButton::indicator {
width: 13px;
height: 13px;
}

QRadioButton::indicator:unchecked {
image: url(:/images/radiobutton_unchecked.png);
}

QRadioButton::indicator:unchecked:hover {
image: url(:/images/radiobutton_unchecked_hover.png);
}

QRadioButton::indicator:unchecked:pressed {
image: url(:/images/radiobutton_unchecked_pressed.png);
}

QRadioButton::indicator:checked {
image: url(:/images/radiobutton_checked.png);
}

QRadioButton::indicator:checked:hover {
image: url(:/images/radiobutton_checked_hover.png);
}

QRadioButton::indicator:checked:pressed {
image: url(:/images/radiobutton_checked_pressed.png);
}
```

当多个选择器间用逗号分割时,他们之间是或逻辑。如下实例中有对 `QPushButton::menu-indicator` 按下或者打开状态下样式的定义:

```css
QPushButton:open { /* when the button has its menu open */
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                    stop: 0 #dadbde, stop: 1 #f6f7fa);
}

QPushButton::menu-indicator {
image: url(menu_indicator.png);
subcontrol-origin: padding;
subcontrol-position: bottom right;
}

QPushButton::menu-indicator:pressed, QPushButton::menu-indicator:open {
position: relative;
top: 2px; left: 2px; /* shift the arrow by 2 px */
}
```



## 动态属性( Dynamic Properties )

动态属性的格式是在选择器后加上带中括号的属性限定表达式,比如通过以下操作将三个输入框的背景色改为黄色:

```cpp
* { background-color: yellow }

QLineEdit *nameEdit = new QLineEdit(this);
nameEdit->setProperty("mandatoryField", true);

QLineEdit *emailEdit = new QLineEdit(this);
emailEdit->setProperty("mandatoryField", true);

QSpinBox *ageSpinBox = new QSpinBox(this);
ageSpinBox->setProperty("mandatoryField", true);

```



## 改变 QPushButton 的默认样式

对 QSS 有个大致的了解后,不妨来做一个实验,修改 QPushButton 的默认样式。一般情况下按钮有三个状态:普通状态、鼠标悬停上方时的状态、鼠标按下后的状态。如果要做出较为复杂的显示效果,一般会以 PNG 图片的形式展现。可以做出三幅图片,也可以将三个状态并列地做在一幅图片上,这里我们选择后者。

!(https://file.mculoop.com/images/2022/11/button_1668590881.png)

图中每个状态的尺寸为 99px * 34px ,正副图的尺寸为 297px * 34px 。虽然在三个状态下使用同一幅图片,但我们要显示不同的图片区间。在样式表里我使用了 border-image ,而没有使用 background ,原因是目前的 background 还不支持对图片的绝对位置的切割。

border-image 是从 CSS3 开始引入的一个非常好用的属性,在 QSS 中同样支持。它的原理是将图片以九宫格的形式分割并绘制在边框矩形中,四个角的四格图片保持不变,可以设置其余格子中图片拉伸、压缩、平铺等,从而保持在变换过程中图片整体更加自然。

!(https://file.mculoop.com/images/2022/11/groovy-border-image-slice_1668590886.png)

!(https://file.mculoop.com/images/2022/11/border-image_1668590890.jpg)

border-image 的语法为:

```
border-image: none | <image> [ <number> | <percentage>]{1,4} [ / <border-width>{1,4} ]? [ stretch | repeat | round ]{0,2}
```

!(https://file.mculoop.com/images/2022/11/border-image-1_1668590894.jpg)

在我们这次的命题里不用边缘部分的 8 个格,只用中间格的切割部分( border: 0px ),各状态切割如下图:

!(https://file.mculoop.com/images/2022/11/4157327730_1668590898.jpg)

<p style="text-align: center;">普通状态切割参数为 0 198 0 0</p>

!(https://file.mculoop.com/images/2022/11/450510152_1668590901.jpg)

<p style="text-align: center;">鼠标悬停上方时的状态切割参数为 0 99 0 99</p>

!(https://file.mculoop.com/images/2022/11/174005229_1668590904.jpg)

<p style="text-align: center;">鼠标按下后的状态切割参数为 0 0 0 198</p>

这次先不使用 .qss 样式表文件,直接在 QtCreator 的部件属性 StyleSheet 里设置:

!(https://file.mculoop.com/images/2022/11/border-image-5_1668590907.jpg)

```css
QPushButton{
color: white;
font: bold 10pt;
border: 0px;
border-image: url(../untitled1/button.png) 0 198 0 0;
}

QPushButton:hover{
border-image: url(../untitled1/button.png) 0 99 0 99;
}

QPushButton:pressed {
border-image: url(../untitled1/button.png) 0 0 0 198;
padding-top: 2px;
padding-left: 2px;
}
```

运行效果:

!(https://file.mculoop.com/images/2022/11/button-click_1668590911.gif)



## 参考资料

1. Qt Style Sheets <http://doc.qt.io/qt-5/stylesheet.html>
2. Qt Style Sheets Reference <http://doc.qt.io/qt-5/stylesheet-reference.html>
3. Qt Style Sheets Examples <http://doc.qt.io/qt-5/stylesheet-examples.html>
4. Border Images <http://www.w3.org/TR/css3-background/#border-images>


页: [1]
查看完整版本: 初识 QSS