上次在公司赶一个运动控制上位机的小版本,我本来以为改改逻辑就完事了。
结果客户现场一句话把我整不会了:
“你们这界面太亮了,厂房里灯一关,屏幕像探照灯,盯久了眼睛疼。”
说实话,工业软件的 UI 很多时候不是“好看”就行,而是耐看、久看不累、信息层级清晰。于是我就开始折腾 Qt6 的暗色主题。
Qt Widgets 的暗色方案大概有三种:
- QPalette(调色板)
- QSS(Qt StyleSheet)
- 直接上现成主题库(QDarkStyleSheet / Fluent / Material 之类)
官方更推荐 QPalette,但我个人更喜欢 QSS:原因很简单——在工业项目里我需要“像 CSS 一样精确控制控件细节”,尤其是按钮、表格、滚动条那种细碎的地方。
这篇我就用一个能跑的最小 demo,把我做暗色主题时的套路和踩坑讲清楚。
1. 我踩的第一个雷:QSS 写了半天,居然没生效
我一开始把 QSS 写在 MainWindow 构造函数里,结果各种控件就是不变色。搞了半天才发现:
- 你得确保在
QApplication创建后再setStyleSheet()(这个倒还好) - 更关键:很多人用的是“局部 setStyleSheet”,结果子控件/弹出菜单样式覆盖得乱七八糟
我的建议:全局一把梭,先把整体基调立住。
1.1 代码:启动时加载 QSS(可直接用)
为什么这么写:
- 主题应该是“应用级配置”,放在启动入口更清晰
- QSS 单独成文件,后面维护/迭代更舒服
// main.cpp
#include <QApplication>
#include <QFile>
#include <QMainWindow>
static QString loadStyleSheet(const QString& path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
return QString::fromUtf8(f.readAll());
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 先保证 DPI/字体这些全局配置就位,再上样式
const QString qss = loadStyleSheet(":/theme/dark-industrial.qss");
if (!qss.isEmpty()) {
app.setStyleSheet(qss);
}
QMainWindow w;
w.resize(1100, 700);
w.show();
return app.exec();
}
写完会得到什么结果:
- 只要资源文件里 QSS 路径没写错,整个应用(包含菜单/输入框/按钮等)会立刻变成暗色基调
- 你后面做细节,就是“在暗色里微调”,不至于越写越乱
⚠️ 我当时最蠢的错误::/theme/dark-industrial.qss 路径写成了 :/themes/...,然后我还怀疑 Qt6 改机制了……其实就是我手滑。
2. 工业风暗色主题:别一上来就写 500 行 QSS
我见过不少项目,QSS 直接堆成 2000 行,谁碰谁崩溃。
我的做法更“土”但好维护:
- 先定义一组颜色 token(类似设计系统)
- 再按控件类型分块写(按钮一块、输入框一块、表格一块)
QSS 本身不支持变量,但我们可以用一个简单技巧:先写成占位符,再在加载时替换。
2.1 代码:给 QSS 加“伪变量”(真香)
为什么这么写:
- 主题迭代最容易变的是颜色,不是选择器
- 有了 token,你就能快速做浅色/暗色双主题
// theme_loader.h(随便放哪里)
#include <QString>
#include <QMap>
static QString applyTokens(QString qss, const QMap<QString, QString>& t) {
for (auto it = t.begin(); it != t.end(); ++it) {
qss.replace(QString("${%1}").arg(it.key()), it.value());
}
return qss;
}
// main.cpp(片段)
QMap<QString, QString> token {
{"bg", "#1E1F22"},
{"panel", "#2B2D30"},
{"border", "#3C3F41"},
{"text", "#D7DAE0"},
{"text2", "#A9AFB9"},
{"accent", "#3D8BFF"},
{"danger", "#E06C75"}
};
QString qss = loadStyleSheet(":/theme/dark-industrial.qss");
qss = applyTokens(qss, token);
app.setStyleSheet(qss);
写完会得到什么结果:
- 你换一套 token,就能把“工业黑”改成“蓝黑”“石墨灰”
- 不用全局搜
#1E1F22搜到眼瞎
3. 关键 QSS 片段:按钮/输入框/表格/滚动条
下面这段就是我项目里反复打磨出来的“能用版”。
为什么要这么写:
- 工业界面最常见的控件就这几类,先把它们搞定
- 滚动条是暗色主题的灵魂之一,不改它就很“默认 Qt”
/* dark-industrial.qss */
QWidget {
background: ${bg};
color: ${text};
font-size: 14px;
}
QMainWindow::separator {
background: ${border};
width: 1px;
height: 1px;
}
/* 面板类容器 */
QFrame, QDockWidget, QGroupBox {
background: ${panel};
border: 1px solid ${border};
border-radius: 6px;
}
/* 输入框 */
QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox {
background: #17181A;
border: 1px solid ${border};
border-radius: 6px;
padding: 6px 10px;
selection-background-color: ${accent};
}
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
border: 1px solid ${accent};
}
/* 按钮 */
QPushButton {
background: #34363A;
border: 1px solid ${border};
border-radius: 6px;
padding: 7px 14px;
}
QPushButton:hover {
border: 1px solid ${accent};
}
QPushButton:pressed {
background: #2C2E31;
}
QPushButton:disabled {
color: ${text2};
background: #2A2B2E;
border: 1px solid #2A2B2E;
}
/* 表格/树 */
QTableView, QTreeView {
background: #17181A;
alternate-background-color: #1B1C1F;
gridline-color: ${border};
border: 1px solid ${border};
border-radius: 6px;
}
QHeaderView::section {
background: ${panel};
color: ${text};
border: 1px solid ${border};
padding: 6px 10px;
}
QTableView::item:selected, QTreeView::item:selected {
background: rgba(61, 139, 255, 0.25);
}
/* 滚动条(不改真的很丑…) */
QScrollBar:vertical {
background: transparent;
width: 10px;
margin: 2px;
}
QScrollBar::handle:vertical {
background: #3A3D41;
border-radius: 5px;
min-height: 30px;
}
QScrollBar::handle:vertical:hover {
background: #4A4E54;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
height: 0px;
}
写完会得到什么结果:
- 控件“灰阶统一”,看起来像工业软件而不是“玩具 demo”
disabled状态不会瞎亮(这个坑我踩过:禁用按钮比启用还显眼,笑死)
4. 我个人的偏好:暗色主题别只靠 QSS,图标也得跟上
虽然官方/教程常讲“QSS 换肤”,但我觉得真正影响观感的是 图标。
暗色背景下:
- 你用黑色 PNG 图标 = 直接隐身
- 你用彩色图标 = 信息噪声爆炸
我目前的做法(还不完美):
- 优先用 SVG(单色/可替换颜色)
- 图标分两套:light/dark
- 或者在 QIcon 里根据主题切换(后面有时间我想把这块做成一套小组件)
5. 最后一个小技巧:用动态属性做“主题开关”
有些客户就是要“一键切换浅色/暗色”,你总不能重启软件。
Qt 的 QSS 支持属性选择器,比如:QWidget[theme="dark"]。
为什么这么写:
- 你可以把“主题状态”挂在根窗口上
- 切换只需要改属性 + 重新 polish
// theme_switch.cpp(片段)
#include <QWidget>
#include <QApplication>
#include <QStyle>
static void repolish(QWidget* w) {
w->style()->unpolish(w);
w->style()->polish(w);
w->update();
}
void setTheme(QWidget* root, const QString& themeName) {
root->setProperty("theme", themeName);
repolish(root);
for (auto child : root->findChildren<QWidget*>()) {
repolish(child);
}
}
结果:
- 切换能立刻生效
- 但也挺蠢的:控件多了会闪一下(这块我还没完全搞明白怎么做到丝滑,后面再研究)
小结(这篇你只要记住这几条)
- 暗色主题别上来就写一堆 QSS:先全局
app.setStyleSheet()把基调立住 - 颜色一定要 token 化,不然维护就是灾难
- 按钮/输入框/表格/滚动条先搞定,工业 UI 基本就稳了
disabled/hover/pressed这些状态不写,最后一定会踩雷- 图标要跟上,不然再好的暗色也像“半成品”
下一篇我准备写《Qt 串口通信避坑指南:QSerialPort 的 5 个常见问题》——这玩意我在项目里踩坑次数,可能比写 UI 还多。
如果这篇对你有用,点个赞、收藏一下,省得下次你也半夜被暗色主题折腾。