Qt 与 GTK 图形框架教程 / 12 - 主题与样式 / Theming
主题与样式 / Theming & Styling
掌握 Qt QSS 和 GTK CSS 主题系统,实现品牌化和暗色模式。 Master Qt QSS and GTK CSS theming for branding and dark mode.
12.1 主题系统对比 / Theming System Comparison
| 特性 / Feature | Qt QSS | GTK CSS |
|---|---|---|
| 语法 | CSS 2.1 子集 | CSS 3 子集 |
| 选择器 | 类型、类、ID、属性、伪状态 | 类型、类、ID、伪类、CSS 节点 |
| 变量 | ❌ 不支持原生变量 | ✅ 自定义变量 |
| 暗色模式 | 手动切换样式表 | 系统自动跟随 |
| 继承 | ✅ 子控件继承 | ✅ CSS 节点树 |
| 动画 | ❌ 无过渡动画 | ✅ 部分支持 |
| 生态 | 个人创作 | Adwaita 基础主题 |
12.2 Qt QSS 主题 / Qt QSS Theming
QSS 基础语法 / QSS Syntax
/* QSS 选择器类型 */
/* 1. 类型选择器 */
QPushButton {
background-color: #3498db;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
}
/* 2. 类选择器(精确类型) */
.QPushButton {
font-weight: bold;
}
/* 3. ID 选择器 */
#submitButton {
background-color: #2ecc71;
}
/* 4. 属性选择器 */
QPushButton[flat="true"] {
background: transparent;
}
QPushButton[cssClass="danger"] {
background-color: #e74c3c;
}
/* 5. 后代选择器 */
QGroupBox QPushButton {
margin: 4px;
}
/* 6. 伪状态 */
QPushButton:hover {
background-color: #2980b9;
}
QPushButton:pressed {
background-color: #21618c;
}
QPushButton:disabled {
background-color: #bdc3c7;
color: #7f8c8d;
}
/* 7. 子控件 */
QComboBox::drop-down {
border: none;
width: 30px;
}
QComboBox::down-arrow {
image: url(:/icons/down-arrow.png);
}
QScrollBar::handle:vertical {
background: #bdc3c7;
border-radius: 4px;
min-height: 30px;
}
完整品牌主题 / Complete Brand Theme
// brandtheme.h - 品牌主题
#ifndef BRANDTHEME_H
#define BRANDTHEME_H
#include <QString>
class BrandTheme {
public:
// 品牌色定义
static constexpr const char* PRIMARY = "#4361ee";
static constexpr const char* SECONDARY = "#3f37c9";
static constexpr const char* SUCCESS = "#2ec4b6";
static constexpr const char* DANGER = "#e71d36";
static constexpr const char* WARNING = "#ff9f1c";
static constexpr const char* BG_LIGHT = "#f8f9fa";
static constexpr const char* BG_DARK = "#212529";
static constexpr const char* TEXT_LIGHT = "#f8f9fa";
static constexpr const char* TEXT_DARK = "#212529";
static QString lightTheme() {
return QStringLiteral(R"(
* {
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
font-size: 14px;
}
QMainWindow, QDialog {
background-color: %1;
}
/* 菜单栏 */
QMenuBar {
background-color: %2;
color: %5;
border-bottom: 1px solid #dee2e6;
padding: 2px;
}
QMenuBar::item:selected {
background-color: %2;
border-radius: 4px;
}
QMenu {
background-color: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 4px;
}
QMenu::item {
padding: 8px 32px;
border-radius: 4px;
}
QMenu::item:selected {
background-color: %2;
color: white;
}
/* 主按钮 */
QPushButton {
background-color: %2;
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-weight: bold;
min-width: 80px;
}
QPushButton:hover { background-color: %3; }
QPushButton:pressed { background-color: %4; }
QPushButton:disabled {
background-color: #adb5bd;
color: #6c757d;
}
/* 危险按钮 */
QPushButton[danger="true"] { background-color: %5; }
QPushButton[danger="true"]:hover { background-color: #c1121f; }
/* 成功按钮 */
QPushButton[success="true"] { background-color: %6; }
QPushButton[success="true"]:hover { background-color: #20a39e; }
/* 输入框 */
QLineEdit, QTextEdit, QSpinBox, QComboBox {
background-color: white;
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 8px 12px;
color: %7;
}
QLineEdit:focus, QTextEdit:focus {
border-color: %2;
}
/* 选项卡 */
QTabWidget::pane {
border: 1px solid #dee2e6;
border-radius: 8px;
background: white;
}
QTabBar::tab {
background: %1;
padding: 10px 20px;
margin-right: 2px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
QTabBar::tab:selected {
background: white;
font-weight: bold;
color: %2;
}
/* 表格 */
QTableView {
border: 1px solid #dee2e6;
border-radius: 8px;
gridline-color: #f1f3f5;
background: white;
}
QTableView::item:selected {
background-color: %2;
color: white;
}
QHeaderView::section {
background-color: %1;
padding: 8px;
border: none;
border-bottom: 2px solid %2;
font-weight: bold;
}
/* 状态栏 */
QStatusBar {
background-color: %4;
color: white;
border-top: 1px solid %3;
}
/* 分组框 */
QGroupBox {
font-weight: bold;
border: 1px solid #dee2e6;
border-radius: 8px;
margin-top: 12px;
padding-top: 20px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 12px;
padding: 0 8px;
color: %2;
}
)").arg(BG_LIGHT, PRIMARY, SECONDARY,
"#3a0ca3", DANGER, SUCCESS, TEXT_DARK);
}
static QString darkTheme() {
return QStringLiteral(R"(
* {
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
font-size: 14px;
}
QMainWindow, QDialog { background-color: %1; }
QMenuBar {
background-color: #2b2d31;
color: %5;
border-bottom: 1px solid #404249;
}
QMenu {
background-color: #2b2d31;
color: %5;
border: 1px solid #404249;
}
QMenu::item:selected {
background-color: %2;
color: white;
}
QPushButton {
background-color: %2;
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-weight: bold;
}
QPushButton:hover { background-color: %3; }
QLineEdit, QTextEdit, QSpinBox, QComboBox {
background-color: #2b2d31;
border: 2px solid #404249;
border-radius: 8px;
padding: 8px 12px;
color: %5;
}
QLineEdit:focus { border-color: %2; }
QTableView {
border: 1px solid #404249;
background-color: #2b2d31;
color: %5;
gridline-color: #404249;
}
QHeaderView::section {
background-color: #313338;
color: %5;
border: none;
border-bottom: 2px solid %2;
}
QStatusBar {
background-color: #1e1f22;
color: %5;
}
)").arg(BG_DARK, PRIMARY, SECONDARY,
"#3a0ca3", TEXT_LIGHT);
}
};
#endif // BRANDTHEME_H
应用主题 / Applying Theme
// main.cpp
#include <QApplication>
#include <QSettings>
#include "brandtheme.h"
void applyTheme(QApplication &app) {
QSettings settings;
bool darkMode = settings.value("ui/darkMode", false).toBool();
if (darkMode) {
app.setStyleSheet(BrandTheme::darkTheme());
} else {
app.setStyleSheet(BrandTheme::lightTheme());
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
applyTheme(app);
MainWindow window;
window.show();
return app.exec();
}
12.3 GTK CSS 主题 / GTK CSS Theming
GTK CSS 节点树 / CSS Node Tree
/* GTK4 CSS 使用 CSS 节点树而非类名 */
/* 控件类型 */
button { }
label { }
entry { }
textview text { }
/* CSS 类 */
.suggested-action { }
.destructive-action { }
.title-1 { }
.title-2 { }
.card { }
.osd { } /* On-Screen Display */
/* 状态 */
button:hover { }
button:active { }
button:checked { }
button:disabled { }
entry:focus { }
/* 自定义类 */
button.my-brand-button {
background-color: @accent_bg_color;
color: @accent_fg_color;
border-radius: 99px;
padding: 10px 24px;
font-weight: bold;
font-size: 14px;
}
button.my-brand-button:hover {
background-color: shade(@accent_bg_color, 0.9);
}
button.my-brand-button:active {
background-color: shade(@accent_bg_color, 0.8);
}
Adwaita 变量 / Adwaita Variables
/* libadwaita 自定义变量 */
@define-color accent_bg_color #3584e4;
@define-color accent_fg_color #ffffff;
@define-color accent_color #3584e4;
@define-color destructive_bg_color #e01b24;
@define-color destructive_fg_color #ffffff;
@define-color success_bg_color #33d17a;
@define-color success_fg_color #000000;
@define-color warning_bg_color #f5c211;
@define-color warning_fg_color #000000;
@define-color window_bg_color #fafafa;
@define-color window_fg_color #2e3436;
@define-color card_bg_color #ffffff;
@define-color card_fg_color #2e3436;
/* 暗色模式自动切换 */
@media (prefers-color-scheme: dark) {
@define-color window_bg_color #242424;
@define-color window_fg_color #ffffff;
@define-color card_bg_color #303030;
}
GTK4 暗色模式 / GTK4 Dark Mode
/* 自动跟随系统暗色模式 */
#include <adwaita.h>
/* libadwaita 自动处理暗色模式切换 */
/* libadwaita handles dark mode automatically */
/* 强制暗色模式 */
AdwStyleManager *manager = adw_style_manager_get_default();
adw_style_manager_set_color_scheme(manager, ADW_COLOR_SCHEME_FORCE_DARK);
/* 运行时切换 CSS */
static void load_theme_css(const char *filename)
{
GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_path(provider, filename);
gtk_style_context_add_provider_for_display(
gdk_display_get_default(),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1); /* 高于默认主题 */
g_object_unref(provider);
}
12.4 暗色模式实现 / Dark Mode Implementation
Qt 暗色模式 / Qt Dark Mode
// darkmode.h
#ifndef DARKMODE_H
#define DARKMODE_H
#include <QApplication>
#include <QSettings>
#include <QStyleFactory>
class DarkMode {
public:
static void toggle(QApplication &app, bool dark) {
QSettings settings;
settings.setValue("ui/darkMode", dark);
if (dark) {
app.setStyle(QStyleFactory::create("Fusion"));
QPalette palette;
palette.setColor(QPalette::Window, QColor(53, 53, 53));
palette.setColor(QPalette::WindowText, Qt::white);
palette.setColor(QPalette::Base, QColor(42, 42, 42));
palette.setColor(QPalette::AlternateBase, QColor(66, 66, 66));
palette.setColor(QPalette::ToolTipBase, Qt::white);
palette.setColor(QPalette::ToolTipText, Qt::white);
palette.setColor(QPalette::Text, Qt::white);
palette.setColor(QPalette::Button, QColor(53, 53, 53));
palette.setColor(QPalette::ButtonText, Qt::white);
palette.setColor(QPalette::BrightText, Qt::red);
palette.setColor(QPalette::Link, QColor(42, 130, 218));
palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
palette.setColor(QPalette::HighlightedText, Qt::black);
palette.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
app.setPalette(palette);
} else {
app.setPalette(QApplication::style()->standardPalette());
}
}
};
#endif
注意事项 / Important Notes
⚠️ QSS 性能 / QSS Performance
复杂 QSS 选择器会影响性能。优先使用
#id选择器。 避免在paintEvent中动态加载 QSS。Complex QSS selectors impact performance. Prefer
#idselectors.
⚠️ GTK CSS 限制 / GTK CSS Limitations
GTK CSS 不支持:flexbox、grid、媒体查询(除
prefers-color-scheme)、position: absolute、z-index、transform。GTK CSS doesn’t support: flexbox, grid, media queries (except
prefers-color-scheme).
⚠️ 跨平台字体 / Cross-Platform Fonts
/* Qt QSS */ * { font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK", sans-serif; }优先使用系统字体,避免嵌入字体文件增加包体积。
扩展阅读 / Further Reading
| 资源 / Resource | 链接 / Link |
|---|---|
| QSS 完整参考 | https://doc.qt.io/qt-6/stylesheet-reference.html |
| GTK4 CSS 文档 | https://docs.gtk.org/gtk4/css-properties.html |
| GNOME 调色板 | https://developer.gnome.org/hig/reference/palette.html |
| Adwaita 颜色 | https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/ |
← 11 - 跨平台开发 | 13 - 数据库集成 →