1. 设计哲学:数据与绘制分离
传统自定义 QStyle 的做法是什么?直接在 drawControl() 里写死颜色。
void MyStyle::drawControl(...) {
QColor bg = QColor("#1e1e1e"); // 硬编码
QColor fg = QColor("#d4d4d4");
painter->fillRect(rect, bg);
painter->setPen(fg);
// ...
}
当你需要深色/浅色切换,或者要套用另一套品牌色时,噩梦就开始了——你得翻遍几十个 draw 函数一个个改。这是 绘制逻辑和视觉参数耦合 的典型症状。
Theme 系统的核心理念很简单:
颜色、尺寸、字体这些"数据"集中管理;绘制函数只负责"怎么画”,不关心"画什么颜色”。
┌─────────────────────────────────────────────┐
│ Theme (数据层) │
│ colors / sizes / fonts / spacings │
└──────────────┬──────────────────────────────┘
│ themeChanged() signal
▼
┌─────────────────────────────────────────────┐
│ QStyle (绘制层) │
│ 只做 painting,通过 theme() 拿当前色值 │
└─────────────────────────────────────────────┘
这样做有三个直接好处:
- 主题切换零代码改动——换掉 Theme 对象,全界面自动刷新。
- 品牌定制只需一套 JSON——甲方要蓝色主色调?改
primaryColor一个字段就行。 - 分支隔离清晰——UI 开发者不用碰 QStyle 代码也能调主题色。
2. 颜色层次模型:四层体系
一个成熟的 UI 框架至少需要 20~40 个设计 token。我们把它们组织成四层,每层有明确的职责边界。
2.1 基础调色板(Base Palette)
界面背景色,通常 4~5 个层次灰度,用于区分容器深度。
QColor backgroundColorMain1; // 最底层(窗体背景)
QColor backgroundColorMain2; // 卡片/面板背景
QColor backgroundColorMain3; // 可交互区域(列表行、输入框底色)
QColor backgroundColorMain4; // 悬浮层/分隔线
QColor neutralColor; // 中性文字色(placeholder、禁用态文字)
层叠关系: Main1 最深 → Main4 最浅(深色主题反之)。这就是 Material Design 里"elevation"概念的简化实现——不需要阴影,靠颜色层次就能区分容器。
2.2 品牌色(Brand Colors)
QColor primaryColor;
QColor primaryColorHovered;
QColor primaryColorPressed;
QColor primaryColorDisabled;
QColor secondaryColor;
QColor secondaryColorHovered;
QColor secondaryColorPressed;
QColor secondaryColorDisabled;
Primary 是主按钮、选中态、焦点等"强调"用色。Secondary 是辅助操作色。两者各自携带 Normal / Hovered / Pressed / Disabled 四态变体。
2.3 语义色(Semantic Colors)
| 字段 | 用途 |
|---|---|
statusColorSuccess |
成功提示、绿色标签 |
statusColorInfo |
信息提示、蓝色标签 |
statusColorWarning |
警告提示、橙色标签 |
statusColorError |
错误提示、红色标签 |
borderColor |
常规边框色 |
focusColor |
焦点指示器颜色(通常即 primaryColor) |
shadowColor |
阴影色(通常带 alpha 的黑色) |
语义色不参与 base palette 的层级逻辑。它们只管"状态传达”——无论深色浅色主题,红色就该表示错误。
2.4 透明变体(Alpha Variants)
QColor backgroundColorMain1Transparent; // ≈ backgroundColorMain1 + alpha≈0.5
QColor backgroundColorMain2Transparent;
QColor backgroundColorMain3Transparent;
QColor backgroundColorMain4Transparent;
QColor neutralColorTransparent;
QColor primaryColorTransparent;
QColor secondaryColorTransparent;
用途:tooltip 背景、modal overlay、水印效果。为什么不直接 color.setAlpha()?因为 JSON 序列化要求存完整 #RRGGBBAA,取用零开销,而且语义明确——读代码的人一眼知道这是"基础色的半透明版本”。
qlementine 参考: 见
Theme.hpp的完整字段列表,实际设计中 transparent 变体是自动计算(或从 JSON 读)的,不需要独立维护两套色值。
3. 交互四态:Normal → Hovered → Pressed → Disabled
这是 GUI 框架最容易做烂的部分。很多项目只有 Normal 和 Disabled,Hovered 和 Pressed 靠代码里临时调 lighter()/darker()。这种方式有两个致命问题:
- 不可预测——
darker(120)在浅色和深色主题下效果完全不同。 - 不可定制——设计师说"hover 态加 10% 饱和度”,你没法在代码里精确表达。
Theme 系统的做法是:每种品牌色显式定义四态,禁止运行时代数运算。
// 以 primary 系列为例,QStyle 中的取色逻辑:
QColor styleColor(ThemeColor role, InteractionState state) {
switch (role) {
case Primary:
switch (state) {
case Normal: return theme.primaryColor;
case Hovered: return theme.primaryColorHovered;
case Pressed: return theme.primaryColorPressed;
case Disabled: return theme.primaryColorDisabled;
}
case Secondary:
// ...
}
}
那么 hover 态是谁触发的?QStyle 的 polish() + QEvent::HoverEnter。 我们在 widget property 上标记 hovered = true,重绘时 Style 检查这个 property 再选择对应颜色。
// 概念示意:
void MyStyle::polish(QWidget* w) {
w->installEventFilter(this);
w->setProperty("hovered", false);
}
bool MyStyle::eventFilter(QObject* obj, QEvent* ev) {
if (ev->type() == QEvent::HoverEnter)
obj->setProperty("hovered", true);
else if (ev->type() == QEvent::HoverLeave)
obj->setProperty("hovered", false);
return QProxyStyle::eventFilter(obj, ev);
}
这样整个状态驱动完全脱离绘制逻辑——Style 只是一个根据 property 查询 theme 再做 painting 的机器。
4. 尺寸 Token:统一节奏
颜色统一了视觉风格,尺寸统一了视觉节奏。以下是核心 token:
// 圆角
double borderRadius; // 卡片、面板的圆角
// 边框
double borderWidth;
// 控件高度(三种规格)
int controlHeightLarge; // 对话框按钮、大输入框
int controlHeightMedium; // 常规按钮、下拉框
int controlHeightSmall; // 标签、小图标按钮
// 图标尺寸
int iconSize;
// 间距
int spacing; // 默认间距(widget margin / layout spacing 基准)
// 字体大小(5 级)
int fontSizeH1; // 标题(dialog title, section header)
int fontSizeH2;
int fontSizeH3; // 正文
int fontSizeH4; // 辅助文字
int fontSizeH5; // 小标签、hint
为什么 H1~H5 而不是用 pt 值? 对 QStyle 来说,fontSizeH1 是一个有语义的 token,不是 16pt。浅色主题可能需要 H3=14px,深色主题可能需要 H3=15px(深色背景下同字号视觉偏小)。语义命名让你可以按主题微调,而不是全局硬编码。
实际使用中,QStyle 通过 ThemeManager::currentTheme() 拿到 token:
QFont MyStyle::standardFont(QWidget* w, Role role) {
QFont f = w->font();
auto& t = ThemeManager::instance().currentTheme();
switch (role) {
case Heading: f.setPixelSize(t.fontSizeH1); break;
case Body: f.setPixelSize(t.fontSizeH3); break;
case Hint: f.setPixelSize(t.fontSizeH5); break;
}
return f;
}
5. JSON 序列化 / 反序列化
QJsonDocument 是 Qt 内置的 JSON 实现,零外部依赖。Theme 类的 load/save 模式如下。
5.1 类声明
// Theme.hpp
#pragma once
#include <QColor>
#include <QString>
#include <QJsonObject>
struct Theme {
// ── Base palette ──
QColor backgroundColorMain1;
QColor backgroundColorMain2;
QColor backgroundColorMain3;
QColor backgroundColorMain4;
QColor neutralColor;
// ── Brand ──
QColor primaryColor;
QColor primaryColorHovered;
QColor primaryColorPressed;
QColor primaryColorDisabled;
QColor secondaryColor;
QColor secondaryColorHovered;
QColor secondaryColorPressed;
QColor secondaryColorDisabled;
// ── Semantic ──
QColor statusColorSuccess;
QColor statusColorInfo;
QColor statusColorWarning;
QColor statusColorError;
QColor borderColor;
QColor focusColor;
QColor shadowColor;
// ── Transparent ──
QColor backgroundColorMain1Transparent;
QColor backgroundColorMain2Transparent;
QColor backgroundColorMain3Transparent;
QColor backgroundColorMain4Transparent;
QColor neutralColorTransparent;
QColor primaryColorTransparent;
QColor secondaryColorTransparent;
// ── Sizes ──
int borderRadius = 6;
int borderWidth = 1;
int controlHeightLarge = 40;
int controlHeightMedium = 32;
int controlHeightSmall = 24;
int iconSize = 16;
int spacing = 8;
int fontSizeH1 = 24;
int fontSizeH2 = 20;
int fontSizeH3 = 16;
int fontSizeH4 = 14;
int fontSizeH5 = 12;
// ── JSON ──
QJsonObject toJson() const;
static Theme fromJson(const QJsonObject& obj);
bool operator==(const Theme& o) const { return toJson() == o.toJson(); }
bool operator!=(const Theme& o) const { return !(*this == o); }
};
5.2 序列化实现(toJson)
// Theme.cpp
#include "Theme.hpp"
#include <QJsonArray>
static QJsonObject colorToJson(const QColor& c) {
QJsonObject obj;
obj["r"] = c.red();
obj["g"] = c.green();
obj["b"] = c.blue();
obj["a"] = c.alpha();
return obj;
}
static QColor colorFromJson(const QJsonObject& obj) {
return QColor(
obj["r"].toInt(),
obj["g"].toInt(),
obj["b"].toInt(),
obj["a"].toInt(255)
);
}
// 批量读写辅助宏 —— 减少手写重复代码
#define THEME_COLOR_TO_JSON(json, theme, name) \
json[QStringLiteral(#name)] = colorToJson(theme.name)
#define THEME_COLOR_FROM_JSON(json, theme, name) \
if (json.contains(QStringLiteral(#name))) \
theme.name = colorFromJson(json[QStringLiteral(#name)].toObject())
#define THEME_INT_TO_JSON(json, theme, name) \
json[QStringLiteral(#name)] = theme.name
#define THEME_INT_FROM_JSON(json, theme, name) \
if (json.contains(QStringLiteral(#name))) \
theme.name = json[QStringLiteral(#name)].toInt()
QJsonObject Theme::toJson() const {
QJsonObject json;
// Colors
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain1);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain2);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain3);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain4);
THEME_COLOR_TO_JSON(json, *this, neutralColor);
THEME_COLOR_TO_JSON(json, *this, primaryColor);
THEME_COLOR_TO_JSON(json, *this, primaryColorHovered);
THEME_COLOR_TO_JSON(json, *this, primaryColorPressed);
THEME_COLOR_TO_JSON(json, *this, primaryColorDisabled);
THEME_COLOR_TO_JSON(json, *this, secondaryColor);
THEME_COLOR_TO_JSON(json, *this, secondaryColorHovered);
THEME_COLOR_TO_JSON(json, *this, secondaryColorPressed);
THEME_COLOR_TO_JSON(json, *this, secondaryColorDisabled);
THEME_COLOR_TO_JSON(json, *this, statusColorSuccess);
THEME_COLOR_TO_JSON(json, *this, statusColorInfo);
THEME_COLOR_TO_JSON(json, *this, statusColorWarning);
THEME_COLOR_TO_JSON(json, *this, statusColorError);
THEME_COLOR_TO_JSON(json, *this, borderColor);
THEME_COLOR_TO_JSON(json, *this, focusColor);
THEME_COLOR_TO_JSON(json, *this, shadowColor);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain1Transparent);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain2Transparent);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain3Transparent);
THEME_COLOR_TO_JSON(json, *this, backgroundColorMain4Transparent);
THEME_COLOR_TO_JSON(json, *this, neutralColorTransparent);
THEME_COLOR_TO_JSON(json, *this, primaryColorTransparent);
THEME_COLOR_TO_JSON(json, *this, secondaryColorTransparent);
// Sizes
THEME_INT_TO_JSON(json, *this, borderRadius);
THEME_INT_TO_JSON(json, *this, borderWidth);
THEME_INT_TO_JSON(json, *this, controlHeightLarge);
THEME_INT_TO_JSON(json, *this, controlHeightMedium);
THEME_INT_TO_JSON(json, *this, controlHeightSmall);
THEME_INT_TO_JSON(json, *this, iconSize);
THEME_INT_TO_JSON(json, *this, spacing);
THEME_INT_TO_JSON(json, *this, fontSizeH1);
THEME_INT_TO_JSON(json, *this, fontSizeH2);
THEME_INT_TO_JSON(json, *this, fontSizeH3);
THEME_INT_TO_JSON(json, *this, fontSizeH4);
THEME_INT_TO_JSON(json, *this, fontSizeH5);
return json;
}
Theme Theme::fromJson(const QJsonObject& json) {
Theme theme;
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain1);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain2);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain3);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain4);
THEME_COLOR_FROM_JSON(json, theme, neutralColor);
THEME_COLOR_FROM_JSON(json, theme, primaryColor);
THEME_COLOR_FROM_JSON(json, theme, primaryColorHovered);
THEME_COLOR_FROM_JSON(json, theme, primaryColorPressed);
THEME_COLOR_FROM_JSON(json, theme, primaryColorDisabled);
THEME_COLOR_FROM_JSON(json, theme, secondaryColor);
THEME_COLOR_FROM_JSON(json, theme, secondaryColorHovered);
THEME_COLOR_FROM_JSON(json, theme, secondaryColorPressed);
THEME_COLOR_FROM_JSON(json, theme, secondaryColorDisabled);
THEME_COLOR_FROM_JSON(json, theme, statusColorSuccess);
THEME_COLOR_FROM_JSON(json, theme, statusColorInfo);
THEME_COLOR_FROM_JSON(json, theme, statusColorWarning);
THEME_COLOR_FROM_JSON(json, theme, statusColorError);
THEME_COLOR_FROM_JSON(json, theme, borderColor);
THEME_COLOR_FROM_JSON(json, theme, focusColor);
THEME_COLOR_FROM_JSON(json, theme, shadowColor);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain1Transparent);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain2Transparent);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain3Transparent);
THEME_COLOR_FROM_JSON(json, theme, backgroundColorMain4Transparent);
THEME_COLOR_FROM_JSON(json, theme, neutralColorTransparent);
THEME_COLOR_FROM_JSON(json, theme, primaryColorTransparent);
THEME_COLOR_FROM_JSON(json, theme, secondaryColorTransparent);
THEME_INT_FROM_JSON(json, theme, borderRadius);
THEME_INT_FROM_JSON(json, theme, borderWidth);
THEME_INT_FROM_JSON(json, theme, controlHeightLarge);
THEME_INT_FROM_JSON(json, theme, controlHeightMedium);
THEME_INT_FROM_JSON(json, theme, controlHeightSmall);
THEME_INT_FROM_JSON(json, theme, iconSize);
THEME_INT_FROM_JSON(json, theme, spacing);
THEME_INT_FROM_JSON(json, theme, fontSizeH1);
THEME_INT_FROM_JSON(json, theme, fontSizeH2);
THEME_INT_FROM_JSON(json, theme, fontSizeH3);
THEME_INT_FROM_JSON(json, theme, fontSizeH4);
THEME_INT_FROM_JSON(json, theme, fontSizeH5);
return theme;
}
5.3 文件级 I/O
#include <QFile>
#include <QJsonDocument>
bool saveThemeToFile(const Theme& theme, const QString& path) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly))
return false;
QJsonDocument doc(theme.toJson());
file.write(doc.toJson(QJsonDocument::Indented));
return true;
}
std::optional<Theme> loadThemeFromFile(const QString& path) {
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
return std::nullopt;
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
if (err.error != QJsonParseError::NoError)
return std::nullopt;
return Theme::fromJson(doc.object());
}
关于宏的使用:THEME_COLOR_TO_JSON 之类的宏大量吞掉样板代码,否则一百多行一对一字段序列化极其丑陋。这是少数"宏比模板好"的场景——纯数据映射,不需要类型多态。
6. ThemeManager:单例 + 信号驱动的热切换
Theme 只是一个数据结构。ThemeManager 提供的是:
- 单例持有当前 Theme
- load/save 包装
themeChanged()信号——这是热切换的关键
// ThemeManager.hpp
#pragma once
#include <QObject>
#include "Theme.hpp"
class ThemeManager : public QObject {
Q_OBJECT
public:
static ThemeManager& instance();
const Theme& currentTheme() const { return _theme; }
void setTheme(const Theme& theme);
bool loadFromFile(const QString& path);
bool saveToFile(const QString& path) const;
signals:
void themeChanged(const Theme& newTheme);
private:
ThemeManager() = default;
Theme _theme;
};
// ThemeManager.cpp
#include "ThemeManager.hpp"
ThemeManager& ThemeManager::instance() {
static ThemeManager mgr;
return mgr;
}
void ThemeManager::setTheme(const Theme& theme) {
if (_theme == theme) return; // 值没变就不刷
_theme = theme;
emit themeChanged(_theme);
}
bool ThemeManager::loadFromFile(const QString& path) {
auto t = loadThemeFromFile(path);
if (!t.has_value()) return false;
setTheme(*t);
return true;
}
bool ThemeManager::saveToFile(const QString& path) const {
return ::saveThemeToFile(_theme, path);
}
QStyle 的消费端:
// 在 MyStyle 构造函数里连接信号:
connect(&ThemeManager::instance(), &ThemeManager::themeChanged,
this, [this](const Theme&) {
// 刷新全部 widget —— 最简单的方式
for (QWidget* w : QApplication::allWidgets())
w->update();
});
QApplication::allWidgets() 遍历所有顶层窗口及子 widget 调用 update()——这比 QApplication::setStyle() 重建整个 style 轻量得多,因为只触发重绘,不重新 polish。
7. 代码练习:最小可运行 Theme 系统
下面给出一个极简但完整的实现。只有 11 个颜色 + 5 个尺寸 token,可以直接编译运行用来验证 Theme 切换机制。
// minimal_theme.hpp
#pragma once
#include <QColor>
#include <QString>
#include <QJsonObject>
struct MinimalTheme {
// ── 12 个颜色 ──
QColor bgMain1 = QColor("#1e1e1e");
QColor bgMain2 = QColor("#252526");
QColor bgMain3 = QColor("#2d2d30");
QColor bgMain4 = QColor("#3e3e42");
QColor textPrimary = QColor("#d4d4d4");
QColor textMuted = QColor("#8a8a8a");
QColor accent = QColor("#0078d4");
QColor accentHover = QColor("#1e8ae5");
QColor accentPress = QColor("#005a9e");
QColor accentDisabled = QColor("#505050");
QColor border = QColor("#3e3e42");
QColor success = QColor("#4ec9b0");
// ── 5 个尺寸 ──
int borderRadius = 4;
int controlHeightMedium = 32;
int iconSize = 16;
int spacing = 8;
int fontSizeBody = 14;
// ── JSON ──
QJsonObject toJson() const;
static MinimalTheme fromJson(const QJsonObject& obj);
bool operator==(const MinimalTheme& o) const;
};
// minimal_theme.cpp
#include "minimal_theme.hpp"
// ── Helper ──
static QJsonObject colorJson(const QColor& c) {
return QJsonObject{{"r", c.red()}, {"g", c.green()}, {"b", c.blue()}, {"a", c.alpha()}};
}
static QColor colorFrom(const QJsonObject& o) {
return QColor(o["r"].toInt(), o["g"].toInt(), o["b"].toInt(), o["a"].toInt(255));
}
QJsonObject MinimalTheme::toJson() const {
return {
{"bgMain1", colorJson(bgMain1)},
{"bgMain2", colorJson(bgMain2)},
{"bgMain3", colorJson(bgMain3)},
{"bgMain4", colorJson(bgMain4)},
{"textPrimary", colorJson(textPrimary)},
{"textMuted", colorJson(textMuted)},
{"accent", colorJson(accent)},
{"accentHover", colorJson(accentHover)},
{"accentPress", colorJson(accentPress)},
{"accentDisabled", colorJson(accentDisabled)},
{"border", colorJson(border)},
{"success", colorJson(success)},
// sizes
{"borderRadius", borderRadius},
{"controlHeightMedium", controlHeightMedium},
{"iconSize", iconSize},
{"spacing", spacing},
{"fontSizeBody", fontSizeBody}
};
}
MinimalTheme MinimalTheme::fromJson(const QJsonObject& j) {
MinimalTheme t;
auto getColor = [&](const QString& k) -> QColor {
return j.contains(k) ? colorFrom(j[k].toObject()) : QColor{};
};
t.bgMain1 = getColor("bgMain1");
t.bgMain2 = getColor("bgMain2");
t.bgMain3 = getColor("bgMain3");
t.bgMain4 = getColor("bgMain4");
t.textPrimary = getColor("textPrimary");
t.textMuted = getColor("textMuted");
t.accent = getColor("accent");
t.accentHover = getColor("accentHover");
t.accentPress = getColor("accentPress");
t.accentDisabled = getColor("accentDisabled");
t.border = getColor("border");
t.success = getColor("success");
auto getInt = [&](const QString& k, int def) -> int {
return j.contains(k) ? j[k].toInt() : def;
};
t.borderRadius = getInt("borderRadius", 4);
t.controlHeightMedium = getInt("controlHeightMedium", 32);
t.iconSize = getInt("iconSize", 16);
t.spacing = getInt("spacing", 8);
t.fontSizeBody = getInt("fontSizeBody", 14);
return t;
}
bool MinimalTheme::operator==(const MinimalTheme& o) const {
return toJson() == o.toJson();
}
// minimal_theme_manager.hpp
#pragma once
#include <QObject>
#include "minimal_theme.hpp"
class MinimalThemeManager : public QObject {
Q_OBJECT
public:
static MinimalThemeManager& instance();
const MinimalTheme& current() const { return _theme; }
void apply(const MinimalTheme& t);
bool loadFromFile(const QString& path);
bool saveToFile(const QString& path) const;
signals:
void changed(const MinimalTheme& t);
private:
MinimalThemeManager() = default;
MinimalTheme _theme;
};
// minimal_theme_manager.cpp
#include "minimal_theme_manager.hpp"
#include <QFile>
#include <QJsonDocument>
MinimalThemeManager& MinimalThemeManager::instance() {
static MinimalThemeManager mgr;
return mgr;
}
void MinimalThemeManager::apply(const MinimalTheme& t) {
if (_theme == t) return;
_theme = t;
emit changed(_theme);
}
bool MinimalThemeManager::loadFromFile(const QString& path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return false;
QJsonParseError err;
auto doc = QJsonDocument::fromJson(f.readAll(), &err);
if (err.error != QJsonParseError::NoError) return false;
apply(MinimalTheme::fromJson(doc.object()));
return true;
}
bool MinimalThemeManager::saveToFile(const QString& path) const {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return false;
f.write(QJsonDocument(_theme.toJson()).toJson(QJsonDocument::Indented));
return true;
}
主程序集成示例
// main.cpp
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QComboBox>
#include "minimal_theme_manager.hpp"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
// 1. 构造两个预设主题
MinimalTheme dark;
// dark 使用上面的默认值 (深色)
MinimalTheme light;
light.bgMain1 = QColor("#f3f3f3");
light.bgMain2 = QColor("#ffffff");
light.bgMain3 = QColor("#e8e8e8");
light.bgMain4 = QColor("#d0d0d0");
light.textPrimary = QColor("#1e1e1e");
light.textMuted = QColor("#666666");
light.accent = QColor("#0078d4");
light.accentHover = QColor("#005a9e");
light.accentPress = QColor("#003d6b");
light.accentDisabled = QColor("#c0c0c0");
light.border = QColor("#d0d0d0");
light.success = QColor("#2a7d4f");
// 2. 创建一个使用主题颜色的 widget(简化示例)
QWidget window;
window.resize(300, 200);
auto& tm = MinimalThemeManager::instance();
tm.apply(dark);
// 连接主题变化 → 重绘
QObject::connect(&tm, &MinimalThemeManager::changed,
&window, [&window]() { window.update(); });
// 3. 切换按钮
QComboBox* combo = new QComboBox(&window);
combo->addItem("Dark");
combo->addItem("Light");
QPushButton* btn = new QPushButton("Test Button", &window);
QVBoxLayout* lay = new QVBoxLayout(&window);
lay->addWidget(combo);
lay->addWidget(btn);
QObject::connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[&](int idx) {
if (idx == 0) tm.apply(dark);
else tm.apply(light);
});
window.show();
return app.exec();
}
编译
# CMakeLists.txt snippet
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
add_executable(theme_demo
minimal_theme.cpp
minimal_theme_manager.cpp
main.cpp
)
target_link_libraries(theme_demo Qt6::Core Qt6::Widgets)
这套代码可以直接编译,验证冷切换和 JSON 持久化。
8. 参考文件
qlementine 中的相关源文件:
| 文件 | 内容 |
|---|---|
Theme.hpp |
Theme 结构体完整字段定义(color + size + spacing token) |
Theme.cpp |
JSON 序列化/反序列化实现 |
ThemeManager.hpp |
单例管理器声明,themeChanged() 信号 |
ThemeManager.cpp |
load/save 逻辑 + 信号发射 |
建议在阅读后续文章前先浏览这些文件,建立完整的字段印象。
下一篇: Style 绘制框架与分层架构——在 Theme 数据层之上,我们将看到 QStyle 如何利用这些 token 构建统一的 draw 函数清单。