QSS介绍
QSS(Qt Style Sheets),Qt的样式表,和CSS非常像的东西。QSS用于为Qt应用程序定义界面元素的视觉样式。通过使用QSS,开发者可以轻松地改变应用程序的外观,而不需要深入了解每个控件的绘制细节。
Box Model(盒模型)
在了解QSS细节前需要知道QSS的基础模型盒模型,它是从CSS来的一个概念(毕竟QSS参考自CSS)。Qt的所有控件可以认为是处在一个盒子中,这个盒子包含4层,从外到内分别是margin(外边距),border(边框),padding(内边距),content(内容)。具体布局如下图:
用Qt的QLabel演示一个具体的例子,这里有一个需要特别注意的,在Qt中如果控件是顶级窗口,那么它会忽略margin属性,margin所有被强制值设置为0。所以我们需要把QLabel放到里一个顶级控件里面,比如一个QWidget。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout layout(&w);
//layout里面4个边距都设置为0,避免干扰分析。
layout.setContentsMargins(0, 0, 0, 0);
QLabel label("Hello", &w);
QString qss = "margin:10px;"
"border:15px;"
"border-style: solid;"
"border-color: blue;"
"padding: 30px;"
"background-color: red;"
"background-clip: content;"
"color: black;";
label.setStyleSheet(qss);
layout.addWidget(&label);
w.show();
return QApplication::exec();
}
运行得到的样子:
其中,白色这个框框就是margin区域,蓝色是border区域,他们都是10px,内层的白色区域就是padding区域,红色框框内就是content区域,这里有几点需要注意:
margin和padding区域都只能指定尺寸,因为它们分别被称为(外边距、内边距,既然是距离那就只能用长度来衡量)。border区域除了可以指定尺寸外,还可以指定颜色和填充风格等其他属性,具体下面会详细介绍。background-color或者background-image会填充padding和content区域。
盒模型四个区域属性详细介绍
margin区域
margin区域只能指定尺寸,指定方式有两种风格:
- 各方向属性分别指定,分别指定4个方向的尺寸,属性分别叫
margin-top、margin-bottom、margin-left、margin-right。:
margin-left:10px;
margin-ringht:15px;
margin-top:25px;
margin-bottom:12px;
- 无方向属性连写格式,只用
margin属性:
//一元指定
margin:10px;/*相当于4个方向统一指定为10px*/
//二元指定
margin:10px 20px;/*相当于分别对垂直(top、bottom)、水平(left、right)方向指定*/
//三元指定
margin:10px 20px 30px;/*按照从top开始的顺时钟方向指定,分别是top、right、bottom、left,缺失的取它对面的值,即这里为:10px 20px 30px 20px。其实二元指定也可以这么解释*/
//四元指定
margin:10px 20px 30px 40px;/*按照从top开始的顺时钟方向指定,分别是top、right、bottom、left*/
padding区域
padding同margin。
border区域
border区域是最为复杂的区域,它同时具有四方向尺寸属性、填充颜色属性、填充风格属性,还具有4个顶点的圆角效果。和margin属性类似,border属性也可以用具体的属性key指定值,也可以按照默认顺序,连续指定多个属性。详细的属性key包括:
border-{top,right,bottom,left}-{width,style,color}
这样的3段构成的属性,其中第一段border固定不变,后面两个段可以取花括号内的某一个值,任意组合,一共4x3=12个三段属性。
还可以是两段构成的属性:
border-{top,right,bottom,left}
border-{width,style,color}
如果省略的是方向,那多个值对应到4个方向的规则参考margin属性的规则。例如:
border-width:5px;/*表示4个方向都为5px*/
border-color:red green;/*表示垂直方向颜色为red,水平方向为green*/
border-style:solid dotted solid dashed;/*按照top, right, bottom, left顺时钟顺序设置4中风格*/
如果省略的是第三段,指定多个值的顺序为width,style,color。
border-left:10px solid red;
我实测Qt5.15好像只能省略颜色,其他省略则不起作用。
如果指保留border一个段,那就是4个方向都相同的值:
border:width style color
例如:
border:10px solid red;
实测,同样发现只能省略颜色,为啥呢?其实是width默认值为0,style默认值为none,color默认值为black。明显使用默认的width和style都会导致你看不到它。
border-style属于Border Style类型,有11种可选值,效果图如下:
需要注意的是如果border-with为0,那么效果相当于border-style为none。如果border-style:double;那么border-width >= 3才能看出效果,很好理解,一根线条的最小尺寸为1px,那么2根最少需要2px,而且还要中间至少1px的间隙,才能让人看出来,所以它要大于等于3。
border-color属于Brush类型,而Brush类型又是Color | Gradient | PaletteRole 中三种方式中选一种。
Color也有多种写法:
- rgb(r, g, b)
- rgba(r, g, b, a)
- hsv(h, s, v)
- hsva(h, s, v, a)
- hsl(h, s, l)
- hsla(h, s, l, a)
- #rrggbb
- Color Name(eg. red green blue black white)
Gradient渐变效果也有3种类型,三种渐变的具体含义见Qt二维图形绘制这篇文章的相关章节:
-
线性渐变
qlineargradient,使用时像调用函数一样,需要指定参数,格式为:qlineargradient( spread:[pad|repeat|reflect], x1:[0-1], y1:[0-1], // 渐变轴线起始坐标(通过百分比计算) x2:[0-1], y2:[0-1], // 渐变轴线结束坐标(通过百分比计算) stop:[0-1] [color], // 插色点 ...);spread含义是指渐变的扩散方式。默认可以不指定,为pad。下面看看3种效果,需要注意的是,要看出3种扩散方式的差异,需要注意插色点位置不要到尽头,即包含stop:1 color;这种插色点。以线性渐变为例:渐变方式 QSS示例 效果图 padbackground-color: qlineargradient(spread:pad,x1:0, y1:0.5, x2:0.5, y2:0.5,stop:0 red,stop:0.5 yellow); ![img]()
repeat同上,仅修改为 spread:repeat![img]()
reflect同上,仅修改为 spread:reflect![img]()
接下来的2对坐标表示渐变的轴线,取值范围为0.0 ~ 1.0。表示它作用区域width或者height百分比位置。后面可以跟多个stop点,每个stop点语法为:
stop:percent color其中percent取值范围也是0.0 ~ 1.0 表示渐变轴线的百分比位置,一般stop组按照percent做升序排布,表示沿着渐变轴线的起始位置向结束位置渐变。每个stop点设置的颜色就是严格和stop的color值一样的,然后从stop1的颜色向下一个点stop2颜色渐变过渡,如此循环。下面为一个示例:border-width: 10px; border-style: solid; border-color: qlineargradient(x1:0.0, y1:0.5, x2:1, y2:0.5, stop:0 red, stop: 0.5 green, stop:1 blue); padding: 10px;效果:
![img]()
-
径向渐变
qradialgradient,同理也有如下格式:qradialgradient( cx:[0-1], cy:[0-1], // 圆心 radius:[0-1], // 半径 fx:[0-1], fy:[0-1], // 焦点位置 stop:[0-1] [color], // 插色点 ...) -
锥形渐变
qconicalgradient,同理也有如下格式:qconicalgradient( cx:[0-1], cy:[0-1], // 中心点 angle:[0-360], // 起始角度(0=12点方向) stop:[0-1] [color], // 插色点 ...);
要理解这些参数含义,最好还是看看Qt二维图形绘制。
PaletteRole
然后是4个顶点的圆角属性设置。首先圆角是啥?我的理解就是往这个直角里面内切一个椭圆的1/4。实现顶点圆润的效果,而不是一个尖锐的直角。具体示意图:
写法也有两种,一种是完整、具体的顶点:border-top-left-radius、border-top-right-radius、border-bottom-right-radius、border-bottom-left-radius;
border-{top,bottom}-{left,right}-radius: width height;
with表示这个1/4椭圆的圆弧边的水平尺寸,height表示它的垂直尺寸。示例如下,如果只有一个值,表示width=height,是一个标准的圆的圆弧。
border-top-left-radius: 10px;
border-top-left-radius: 10px 20px;
还一种是连写格式的border-radius,
border-radius: width height;
width、height含义同上,这样表示4个顶点的圆弧都一样。这种写法好像不能单独指定某一个顶点的圆弧,只能统一指定。
conntent区域
内容区域可设置的东西也比较多,主要是3个方面:
文本控制属性
| 属性 | 值示例 | 说明 |
|---|---|---|
color |
#ff0000rgb(255,0,0)red |
文本颜色 |
font |
bold 14px "Arial" |
字体系列、大小和样式 |
font-weight |
boldnormal800 |
字体粗细 |
font-style |
normalitalic oblique |
字体样式 |
font-size |
12px1.2em |
字体大小 |
text-align |
topbottomcenterleftright |
文本对齐方式 |
text-decoration |
underlineoverlineline-throughnone |
文本装饰线 |
首先便是文字的颜色color属性,就是一个简单的颜色值,然后是字体,它也有具体属性和连写格式。具体属性包括:
font-style
它用于设置文字风格:
| 值 | 效果 |
|---|---|
normal |
正常 |
italic |
斜体 |
oblique |
斜体 |
看上去
italic和oblique都是斜体,那有什么区别呢?这就要看斜体的实现方式,有些字库里面自带了斜体风格的字体,那么用italic,有些字库里面没带,设置为italic会没效果,这时就要就要靠算法模拟出斜体,此时用oblique。
font-weight
它用于设置文字线条的粗细。
font-size
字体大小,单位可以是像素px或者pt。
font-family
字体类型,可以通过fc-list查看系统有哪些可用的字体。
连写方式就是按照如下顺序:
font:style weight size family;
除size外,其他都可以省略,使用系统默认中。
然后是一些文字的布局和装饰属性:
text-align
文本对齐属性,支持top,bottom,left,right,center几种值或者合理的组合,比如 top left 表示左上对齐,并且需要注意该属性只对部分支持内部布局的控件有效,并非普适性的。
text-decoration
给文本加装饰线,可以指定一个或者多个值。
| 值 | 效果 |
|---|---|
underline |
下划线 |
overline |
上划线 |
line-through |
中划线 |
none |
无划线效果 |
背景样式属性
| 属性 | 值示例 | 说明 |
|---|---|---|
background-color |
#3498dbtransparent |
背景颜色 |
background-image |
url(:/images/bg.png) |
背景图像 |
background-repeat |
repeat-xno-repeatrepeat-yrepeat-xy |
背景图像重复方式 |
background-position |
centertop left50% 30% |
背景图像位置 |
background-clip |
contentborderpadding |
背景绘制区域 |
background-origin |
contentpaddingborder |
背景起始位置 |
background-attachment* |
fixedscroll |
*(Qt 5.12后支持) |
下面是几个具体的属性效果图:
background-clip
该属性用于限定background-color的显示区域。

值分别是border(默认),padding,content。其中绿色虚线框就是pandding区域和border区域的界线。值为content时很好理解,只填充到content区域,padding区域是不填充的,但是border和padding的效果要非常仔细的观察,前者背景色渗透到了这根绿色界线上了,后者没渗透过去。
background-repeat
该属性仅在指定了正确的background-image时才有效。

这几个效果分别是repeat-xy(默认,Qt官方文档说是repeat,但是我实际测试repeat==no-repeat), no-repeat, repeat-x, repeat-y。注意只有图片尺寸小于content区域时才可以重复,否则没效果。
background-position
该属性仅在指定了正确的background-image,并且Background-repeat值不为repeat-xy时才有效。它的取值是Alignment类型,具有{ top | bottom | left | right | center }等值,或者它们的组合,如下图所示。

- 当
background-repeat:no-repeat;时,可以指定这个背景图片在这9个区域中的任意一个。- 当
background-repeat:repeat-x;时,background-position的值仅垂直方向的top,center,bottom有作用。- 当
background-repeat:repeat-y;时,background-position的值仅水平方向的left,center,right有作用。- 当
background-repeat:repeat-xy;时,background-position的值将失去意义。
background-origin
该属性和background-clip类似,不过一个限定的是background-color,一个限定的是background-image。
background-attachment
该属性主要针对继承自QAbstractScrollArea的类,表示滚动时,背景图片是固定(fixed)还是跟着滚动(scroll)。
尺寸和布局属性
| 属性 | 类型 | 说明 |
|---|---|---|
| width | Lenth | 表示内容区域的宽度,通常会被hintSize覆盖 |
| height | Lenth | 表示内容区域的高度,通常会被hintSize覆盖 |
| {min,max}-width | Lenth | 表示内容区域最大或最小宽度,会限定值只能在这个区间内 |
| {min,max}-height | Lenth | 表示内容区域最大或最小高,会限定值只能在这个区间内 |
| spacing | Lenth | 表示控件内部子元素之间的间隙距离,通常用于容器控件或者内部包含多个子控件的复合控件 |
如何使用QSS
在Qt中使用QSS的方式大致分为两种,一种是控件自己调用它的setStyleSheet方法,另一种是加载全局的QSS文件。
- 方式一:
QWidget w;
w.setStyleSheet("padding:10px;background-color:black");
- 方式二:
void loadQss()
{
QFile qssFile("../sytle.qss");
if (qssFile.open(QFile::ReadOnly))
{
QByteArray qss = qssFile.readAll();
qApp->setStyleSheet(qss);
qssFile.close();
}
}
特定对象的样式表的属性会覆盖全局的样式表对应的属性(合并效果),特定对象多次调用setStyleSheet出现的现象是后面的覆盖前面的(覆盖效果)。详细的对比为:
| 特性 | 全局样式表 (qApp->setStyleSheet) |
局部样式表 (widget->setStyleSheet) |
|---|---|---|
| 优先级 | 低(作为默认生效) | 高(覆盖全局和其他继承样式) |
| 作用范围 | 所有符合选择器的控件 | 单个控件及其子控件(依赖选择器逻辑) |
| 属性继承 | ✓ 未设置的属性可从全局继承 | ⨯ 清除原始样式,需手动合并 |
| 维护灵活性 | 方便统一管理基础样式 | 灵活定制个别控件的特殊样式 |
QSS语法介绍
下面内容基本翻译摘抄自Qt官方文档内容QSS语法。
样式规则
QSS样式表由一系列样式规则组成。一个样式规则由选择器和声明组成。选择器指定了受该规则影响的控件;声明指定了应设置在控件上的属性。格式如下:
选择器:{
属性1:值;
属性2:值1 值2;
...
}
示例:
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 }
样式规则的声明部分是一系列 属性 : 值 对,用大括号 ({}) 包围,并用分号分隔。例如:
QPushButton { color: red; background-color: white }
选择器类型
到目前为止,所有示例都使用了最简单的选择器类型,即类型选择器。Qt 样式表支持CSS2中定义的所有选择器 。下表总结了最常用的选择器类型。
| 选择器 | 示例 | 说明 |
|---|---|---|
| 通配选择器 | * | 匹配所有部件 |
| 类型选择器 | QPushButton | 匹配 QPushButton 及其子类的实例。 |
| 属性选择器 | QPushButton[flat="false"] | 匹配不是 QPushButton 的 扁平 实例。 |
| 类选择器 | .QPushButton | 匹配 QPushButton 的实例,但不包括其子类。这等同于 *[class~="QPushButton"]。 |
| ID选择器 | QPushButton#okButton | 匹配所有对象名称为okButton,类型为QPushButton的实例。 |
| 后代选择器 | QDialog QPushButton | 匹配所有作为 QDialog 的后代(子代、孙代等)的 QPushButton 实例。 |
| 子选择器 | QDialog > QPushButton | 匹配所有直接属于 QDialog 的 QPushButton 实例。 |
通配选择器:
通常用于公用的属性,例如背景颜色,字体,字体大小,前景颜色等。以保证UI界面风格统一。
类型选择器:
就是直接用控件类名作为选择器名,匹配所有这种类型的实例以及它子类的实例。匹配子类是因为Qt的样式表子类如果未明确指定,那么会继承父类的。
属性选择器:
所谓属性选择器,就是选择器后跟一个属性表达式,表达式为真,则匹配上。这里提到属性表达式前是一个选择器,因此它可以是任何选择器,甚至包括属性选择器,而上面表格中看上去好像只能是类型选择器,实际不对!因此下面的写法都正确:
*[flat="false"] { key:value;...;}
QPushButton[default="true"] { key:value;...;}
/*关于连续的属性表达式可以理解为表达式相与*/
QPushButton[default="true"][flat="false"]{ key:value;...;}
QPushButton#pbtn_ok[default="true"] { key:value;...;}
...
...
属性表达式中值通常以字符串形式书写,但是我发现属性为boolean类型是,可以省略双引号"。也不知道这鬼东西为什么这么混乱!!
属性又包括静态属性和动态属性。静态属性可以看类的头文件,以QPushButton为例:
class Q_WIDGETS_EXPORT QPushButton : public QAbstractButton
{
Q_OBJECT
Q_PROPERTY(bool autoDefault READ autoDefault WRITE setAutoDefault)
Q_PROPERTY(bool default READ isDefault WRITE setDefault)
Q_PROPERTY(bool flat READ isFlat WRITE setFlat)
}
当然也要注意,该类祖先的静态属性也可以被它用来做属性表达式。例如已QPushButton为例,它有个老祖宗QObject,里面有声明了如下静态属性:
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
}
objectName静态属性,因此我们属性表达式可以使用对象名(也就是ID选择器中的对象id),因此它们两个可以达到一样的效果:
QPushButton[objectName="pbtn_ok"]{ }
QPushButton#pbtn_ok{ }
也可以通过Designer添加动态属性:

它实际是调用继承自QObject的方法。
bool setProperty(const char *name, const QVariant &value);
对于动态属性,需要注意的是属性改变后,可能需要强制重新加载样式。例如:
// 通过定时器反转test属性状态
connect(timer, &QTimer::timeout, this, [&]() {
ui->pbtn->setProperty("test", !ui->pbtn->property("test").toBool());
// 卸载之前样式
ui->pbtn->style()->unpolish(ui->pbtn);
// 重新加载样式
ui->pbtn->style()->polish(ui->pbtn);
});
timer->start(1000);
属性表达式的操作符有多种:
| 操作符 | 含义 | 实例 | 详细介绍 |
|---|---|---|---|
| [attr] | 是否存在 | [flat] | 就是只要包含该属性即可,不关心属性的值 |
| = | 等于 | [flat="true"] | 精准匹配属性值 |
| ^= | 以值开头 | [myProperty^="te"] | 了解正则表达式都清楚,^表示行匹配的开头处 |
| $= | 以值结尾 | [myProperty^="st"] | 了解正则表达式都清楚,$表示行匹配的结尾处 |
| ~= | 包含列表值 | [myProperty~="ok"] | 属性值中如果包含多个以空格分开的字符串,那么只要任意单词匹配上就成功。 比如属性表达式为[myProperty~="ok"]时,"state ok"、"status ok", 都包含ok,都会匹配上,但是 token匹配不上。 |
| *= | 包含子串 | [myProperty~="es"] | 包含子串如test state都会被st或者te子串匹配上 |
| |= | 连字符匹配 | [myProperty|="lang"] | 所谓连字符匹配就是这里例子中会匹配上lang,lang-en, lang-zh,但是不会匹配上 language。看这个例子也知道它一般用于语言匹配,区域匹配等场景 |
Qt官方文档我只看到介绍了=和~=,其他的几种我是从网上找到的例子,并且看了qcss解析的源代码后才确定支持这几种:
bool Parser::parseAttrib(AttributeSelector *attr)
{
skipSpace();
if (!next(IDENT)) return false;
attr->name = lexem();
skipSpace();
if (test(EQUAL)) { // [attr=value]
attr->valueMatchCriterium = AttributeSelector::MatchEqual;
} else if (test(INCLUDES)) { // [attr~=value]
attr->valueMatchCriterium = AttributeSelector::MatchIncludes;
} else if (test(DASHMATCH)) { // [attr|=value]
attr->valueMatchCriterium = AttributeSelector::MatchDashMatch;
} else if (test(BEGINSWITH)) { // [attr^=value]
attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
} else if (test(ENDSWITH)) { // [attr$=value]
attr->valueMatchCriterium = AttributeSelector::MatchEndsWith;
} else if (test(CONTAINS)) { // [attr*=value]
attr->valueMatchCriterium = AttributeSelector::MatchContains;
} else {
return next(RBRACKET);
}
skipSpace();
if (!test(IDENT) && !test(STRING)) return false;
attr->value = unquotedLexem();
skipSpace();
return next(RBRACKET);
}
相关源码,枚举定义位于qtbase/src/gui/text/qcssparser.cpp,qtbase/src/gui/text/qcssparser_p.h, qtbase/src/gui/text/qcssparser.cpp 里面。
需要注意的是,使用属性表达式时有几个小坑需要注意:
- 选择器和属性表达式之间不要有空格,
css QPushButton [flat="false]{ color:red;}- 属性表达式如果为
~=时,属性和操作符之间不能有空格。css QPushButton[myProperty ~= "hello" ]{color:red;}
我的测试验证版本是Qt5.15,发行版是Ubuntu22.04。这多半是一个bug。
类选择器
类选择器就是严格匹配类,不包括它的子类。
ID选择器
所谓ID就是指对象的objectName,每次就是仅针对objectName属性匹配,上面也介绍了,用属性选择器可以到达同样的效果。通常Id相同的话(即控件变量名),他们不会出现在同一个UI内,因为冲突了嘛, 举几个例子:
/*下面两种等效*/
#myId{color:red;}
*#myId{color:red;}
/*限定特定类型*/
QPushButton#myId{color:red;}
/*和属性选择器组合*/
QPushButton#commonid[flat="true"]{
color:red;
}
后代选择器
后代选择器Ancestors Descendants指的是具体对象的父子关系,而不是继承关系,理解这点非常重要。例如有如下自定义控件:
class MyButton : public QPushButton
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
~MyButton() override;
};
然后往界面中分别添加QPushButton和MyButton的按钮, 如果使用如下样式表
* {
color: blue;
}
QPushButton MyButton{
color: red;
}
最终的效果是这样:

没达到预期的效果,我原来的理解是Mybutton不是继承于QPushButton吗?这里按道理匹配得上。认真阅读,才发现,是Ancestors要和Descendants有血缘关系(这是针对对象),而不是继承关系(这是针对类)。甚至Descendants的类型都可以不是Ancestors的派生类型。
只要Ancestors是Descendants的祖先对象即可。
例如如下例子:
#include <QApplication>
#include <QFile>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include "mainwidget.h"
void loadQss()
{
QFile qssFile("../sytle.qss");
if (qssFile.open(QFile::ReadOnly))
{
QByteArray qss = qssFile.readAll();
qApp->setStyleSheet(qss);
qssFile.close();
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
loadQss();
QWidget w;
QVBoxLayout layout(&w);
QPushButton button("hello");
layout.addWidget(&button);
QLabel label("world");
layout.addWidget(&label);
w.show();
return QApplication::exec();
}
往QWidget w分别添加了两个子对象button和label。然后使用如下样式表:
* {
color: blue;
}
QWidget QPushButton{
color: red;
}
最终效果:

所以该选择器含义是:所有属于QWidget对象的子对象的QPushButton对象匹配上。当然它也可以有如下写法:
/*省略或者通配祖先对象类型*/
QPushButton{color:red;}
* QPushButton{color:red;}
/*省略或者通配字子对象类型*/
QWidget {color:red;}
QWidget *{color:red;}
/*标准形式*/
QWidget QPushButton{
color: red;
}
/*组合形式*/
QWidget QPushButton[flat="false"]{
color: red;
}





浙公网安备 33010602011771号