QSS 教学示例重构:双主题样式抽离为外部 .qss 资源(QFile 运行时加载)

把原先硬编码在 mainwindow.cpp 里的 kLightQss / kDarkQss 两个 C++ 字符串,
拆成独立的 themes/light.qss、themes/dark.qss,由 themes.qrc(AUTORCC)编译进
可执行文件,applyStyle() 用 QFile 读取后 qApp->setStyleSheet 应用。

教学要点(QSS 最佳实践):
- 样式与逻辑彻底分离:设计师/非程序员可直接编辑 .qss,不必碰 C++;
- 开发期改 .qss 重跑即生效,不必改 C++ 重编译;
- 多主题物理分文件,编辑器对 .qss 有语法高亮。

README 同步更新差异表、最佳实践条目与目录结构。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
张宗平
2026-07-09 16:50:31 +08:00
parent 82b726ab3e
commit 12950da129
5 changed files with 141 additions and 99 deletions
@@ -11,6 +11,7 @@
#include <QApplication>
#include <QDebug>
#include <QDockWidget>
#include <QFile>
#include <QKeySequence>
#include <QListWidget>
#include <QListWidgetItem>
@@ -19,84 +20,22 @@
#include <QMessageBox>
#include <QStatusBar>
// ---- QSS按按键功能分色,ID 选择器精确定位(widget.ui 里已设好 objectName----
// 教学要点QSS 选择器认的是 objectName——setObjectName("btnEquals") 之后才能用
// #btnEquals{...}。本例的 objectName 在 widget.ui 里已定义,无需再 setObjectName。
// ---- QSS从外部 .qss 资源文件加载(themes/light.qss / themes/dark.qss----
// 教学要点QSS 最佳实践):把样式抽成独立 .qss 文件用 QFile 读取,而非硬编码
// C++ 字符串——① 样式与逻辑彻底分离(设计师可独立编辑 .qss);② 开发期改 .qss
// 重跑即生效,不必改 C++ 重编译;③ 多主题物理分文件,编辑器对 .qss 有语法高亮。
// 资源路径 :/themes/*.qss 由 themes.qrcAUTORCC)编译进可执行文件。
namespace {
// 浅色主题:数字=浅蓝、运算=橙、等号=绿、括号=灰、清空/退格=红、显示区=米黄
const char* kLightQss = R"(
QMainWindow { background: #f5f5f5; }
QLineEdit#display, QTextEdit#expressionEdit {
background: #fffbe6; color: #222;
border: 1px solid #d0c8a0; border-radius: 4px;
font-size: 18px; padding: 4px;
// 从嵌入资源读取样式表。读取失败返回空串(setStyleSheet 空串即清空样式)。
QString loadQss(const QString& resourcePath) {
QFile f(resourcePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "无法打开样式资源" << resourcePath;
return {};
}
return QString::fromUtf8(f.readAll());
}
QPushButton {
border: 1px solid #bbb; border-radius: 4px;
padding: 8px; font-size: 15px; min-height: 28px;
}
QPushButton#btn0, QPushButton#btn1, QPushButton#btn2, QPushButton#btn3,
QPushButton#btn4, QPushButton#btn5, QPushButton#btn6, QPushButton#btn7,
QPushButton#btn8, QPushButton#btn9, QPushButton#btnDot {
background: #e3f2fd; border-color: #90caf9; color: #0d47a1;
}
QPushButton#btnPlus, QPushButton#btnMinus, QPushButton#btnMul, QPushButton#btnDiv {
background: #ffe0b2; border-color: #ffb74d; color: #e65100;
}
QPushButton#btnEquals {
background: #c8e6c9; border-color: #81c784; color: #1b5e20; font-weight: bold;
}
QPushButton#btnLeftParen, QPushButton#btnRightParen {
background: #eceff1; border-color: #b0bec5; color: #37474f;
}
QPushButton#btnClear, QPushButton#btnBackspace {
background: #ffcdd2; border-color: #e57373; color: #b71c1c;
}
QPushButton:hover { background: #ddd; }
QPushButton:pressed { background: #bbb; }
QListWidget { background: #fafafa; alternate-background-color: #eeeeee; }
QStatusBar { background: #e0e0e0; }
QMenuBar { background: #eceff1; }
QDockWidget { color: #333; }
)";
// 深色主题:整体深底浅字,功能分色保留但调暗,便于和浅色对照验证「切换生效」
const char* kDarkQss = R"(
QMainWindow { background: #2b2b2b; color: #eee; }
QLineEdit#display, QTextEdit#expressionEdit {
background: #3a3a2a; color: #fffbe6;
border: 1px solid #6b6b4a; border-radius: 4px;
font-size: 18px; padding: 4px;
}
QPushButton {
border: 1px solid #555; border-radius: 4px;
padding: 8px; font-size: 15px; min-height: 28px; color: #eee;
}
QPushButton#btn0, QPushButton#btn1, QPushButton#btn2, QPushButton#btn3,
QPushButton#btn4, QPushButton#btn5, QPushButton#btn6, QPushButton#btn7,
QPushButton#btn8, QPushButton#btn9, QPushButton#btnDot {
background: #1565c0; border-color: #42a5f5; color: #e3f2fd;
}
QPushButton#btnPlus, QPushButton#btnMinus, QPushButton#btnMul, QPushButton#btnDiv {
background: #d84315; border-color: #ff7043; color: #ffe0b2;
}
QPushButton#btnEquals {
background: #2e7d32; border-color: #66bb6a; color: #c8e6c9; font-weight: bold;
}
QPushButton#btnLeftParen, QPushButton#btnRightParen {
background: #455a64; border-color: #78909c; color: #eceff1;
}
QPushButton#btnClear, QPushButton#btnBackspace {
background: #c62828; border-color: #ef5350; color: #ffcdd2;
}
QPushButton:hover { background: #666; }
QPushButton:pressed { background: #888; }
QListWidget { background: #333; alternate-background-color: #3d3d3d; color: #eee; }
QStatusBar { background: #1f1f1f; color: #eee; }
QMenuBar { background: #37474f; color: #eee; }
QDockWidget { color: #eee; }
)";
} // namespace
@@ -168,7 +107,9 @@ void MainWindow::refreshStatus(const QString& msg) {
// 避免和未来某控件局部样式打架(Day3 下午坑卡:setStyleSheet 级联失控)。
void MainWindow::applyStyle(bool dark) {
m_dark = dark;
qApp->setStyleSheet(QString::fromUtf8(dark ? kDarkQss : kLightQss));
const QString path = dark ? QStringLiteral(":/themes/dark.qss")
: QStringLiteral(":/themes/light.qss");
qApp->setStyleSheet(loadQss(path));
}
void MainWindow::onToggleTheme(bool dark) {