强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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

特性 / FeatureQt QSSGTK 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 #id selectors.

⚠️ GTK CSS 限制 / GTK CSS Limitations

GTK CSS 不支持:flexbox、grid、媒体查询(除 prefers-color-scheme)、 position: absolutez-indextransform

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 - 数据库集成