Qt6 暗色主题实战:用 QSS 打造工业风深色界面

7 阅读5分钟

上次在公司赶一个运动控制上位机的小版本,我本来以为改改逻辑就完事了。

结果客户现场一句话把我整不会了:

“你们这界面太亮了,厂房里灯一关,屏幕像探照灯,盯久了眼睛疼。”

说实话,工业软件的 UI 很多时候不是“好看”就行,而是耐看、久看不累、信息层级清晰。于是我就开始折腾 Qt6 的暗色主题。

Qt Widgets 的暗色方案大概有三种:

  1. QPalette(调色板)
  2. QSS(Qt StyleSheet)
  3. 直接上现成主题库(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 还多。

如果这篇对你有用,点个赞、收藏一下,省得下次你也半夜被暗色主题折腾。