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:
@@ -5,38 +5,55 @@
|
|||||||
|
|
||||||
## 定位:上午版 + QSS 美化
|
## 定位:上午版 + QSS 美化
|
||||||
|
|
||||||
以 [`../calculator_mainwindow/`](../calculator_mainwindow/README.md)(Day3 上午版)为
|
以 [`../calculator_mainwindow/`](../calculator_mainwindow/README.md)(Day3 上午版)为起点,
|
||||||
起点,**只加 QSS**:按键按功能分色、`:hover`/`:pressed` 视觉反馈、深/浅双主题切换。
|
**只加 QSS**:按键按功能分色、`:hover`/`:pressed` 视觉反馈、深/浅双主题切换。
|
||||||
菜单/停靠窗/状态栏/计算逻辑与上午版完全一致——**QSS 与逻辑正交**,这正是 Day3 下午
|
菜单/停靠窗/状态栏/计算逻辑与上午版完全一致——**QSS 与逻辑正交**,这正是 Day3 下午
|
||||||
「美化」环节要传达的核心。
|
「美化」环节要传达的核心。
|
||||||
|
|
||||||
## 与上午版的差异(集中在 `mainwindow.{h,cpp}`)
|
## 与上午版的差异(集中在 `mainwindow.{h,cpp}` + `themes/`)
|
||||||
|
|
||||||
| 改动 | 位置 | 内容 |
|
| 改动 | 位置 | 内容 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 双主题 QSS 字符串 | `mainwindow.cpp` 顶部 `kLightQss` / `kDarkQss` | 按键分色 + 伪状态 |
|
| 双主题样式表(**外部 `.qss` 文件**) | `themes/light.qss`、`themes/dark.qss` | 按键分色 + 伪状态 |
|
||||||
| 视图菜单「深色主题」 | `buildMenu()` | `checkable` QAction,`toggled` → `onToggleTheme` |
|
| 资源清单 | `themes.qrc`(`AUTORCC`) | 嵌入 `:/themes/light.qss`、`:/themes/dark.qss` |
|
||||||
| `applyStyle(bool dark)` | `mainwindow.cpp` | `qApp->setStyleSheet(...)` 全局切换 |
|
| 视图菜单「深色主题」 | `buildMenu()` | `checkable` QAction,`toggled → onToggleTheme` |
|
||||||
| 自测加主题断言 | `runSelfTest()` | 验证切换后 `styleSheet()` 含对应主题标识 |
|
| `applyStyle(bool dark)` | `mainwindow.cpp` | `QFile` 读 `.qss` → `qApp->setStyleSheet` 全局切换 |
|
||||||
|
| 自测加主题断言 | `runSelfTest()` | 切换后 `styleSheet()` 含对应主题标识 |
|
||||||
|
|
||||||
`calculatordesigner.{h,cpp}` / `expressionevaluator.{h,cpp}` / `widget.ui` 与上午版
|
`calculatordesigner.{h,cpp}` / `expressionevaluator.{h,cpp}` / `widget.ui` 与上午版完全一致。
|
||||||
完全一致(自包含复制)。
|
|
||||||
|
|
||||||
## QSS 教学要点(对应 OUTLINE_4DAY.md Day3 下午)
|
## QSS 最佳实践(本示例的刻意设计)
|
||||||
|
|
||||||
1. **ID 选择器认 objectName**:`QPushButton#btnEquals { ... }` 命中的前提是控件
|
1. **样式抽成外部 `.qss` 文件**(而非硬编码 C++ 字符串):`light.qss` / `dark.qss` 是纯样式,
|
||||||
`setObjectName("btnEquals")`。本例的 objectName 在 `widget.ui` 里已定义
|
由 `themes.qrc` 编译进可执行文件,`mainwindow.cpp` 用 `QFile` 读取后 `qApp->setStyleSheet`
|
||||||
(`<widget class="QPushButton" name="btnEquals">`),无需再 `setObjectName`——
|
应用。好处:
|
||||||
这是「样式写了没生效」头号原因(忘设 objectName)的反面教材。
|
- 设计师/非程序员可直接编辑 `.qss`(不用碰 C++);
|
||||||
2. **按键分色用功能归类**:数字键(`btn0`–`btn9`,`btnDot`)浅蓝、运算键
|
- 开发期改 `.qss` 重跑即生效(不必改 C++ 重编译);
|
||||||
(`btnPlus/Minus/Mul/Div`)橙、等号(`btnEquals`)绿、括号灰、清空/退格红——
|
- 多主题物理分文件,编辑器对 `.qss` 有语法高亮。
|
||||||
用 ID 选择器分组,比给每个按钮单独写样式更易维护。
|
2. **ID 选择器认 objectName**:`QPushButton#btnEquals { ... }` 命中的前提是控件
|
||||||
3. **伪状态**:`:hover` / `:pressed` 不用手写鼠标事件就能做交互反馈。
|
`setObjectName("btnEquals")`——本例 objectName 在 `widget.ui` 定义
|
||||||
4. **`qApp->setStyleSheet` 全局 vs `widget->setStyleSheet` 局部**:本例用全局,
|
(`<widget class="QPushButton" name="btnEquals">`)。这是「样式写了没生效」头号原因
|
||||||
一次切换全部生效。坑卡:全局样式会被 widget 级样式覆盖,混用易级联失控
|
(忘设 objectName)的反面教材。
|
||||||
(OUTLINE_4DAY.md Day3 下午「QSS 级联失控」卡)。
|
3. **按键分色用功能归类**(DRY):数字键(`btn0`–`btn9`,`btnDot`)浅蓝、运算键橙、等号绿、
|
||||||
5. **主题切换不碰逻辑**:`applyStyle` 只改 `styleSheet`,不动任何业务代码——
|
括号灰、清空退格红——用 ID 选择器分组,比给每个按钮单独写样式更易维护。
|
||||||
QSS 与逻辑正交的可维护性演示。
|
4. **伪状态**:`:hover` / `:pressed` 不用手写鼠标事件就能做交互反馈。
|
||||||
|
5. **`qApp->setStyleSheet` 全局 vs `widget->setStyleSheet` 局部**:本例用全局,一次切换全部
|
||||||
|
生效。坑卡:全局样式会被 widget 级样式覆盖,混用易级联失控——故本例只用全局。
|
||||||
|
6. **主题切换不碰逻辑**:`applyStyle` 只改 `styleSheet`,不动任何业务代码。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
```
|
||||||
|
calculator_mainwindow_qss/
|
||||||
|
├── mainwindow.{h,cpp} # 上午版 + applyStyle(loadQss) 主题切换
|
||||||
|
├── calculatordesigner.{h,cpp} # 计算器面板(与上午版一致)
|
||||||
|
├── expressionevaluator.{h,cpp} # 表达式求值(与上午版一致)
|
||||||
|
├── widget.ui # 面板表单(objectName 在此定义)
|
||||||
|
├── themes.qrc # 资源清单:嵌入两个 .qss
|
||||||
|
├── themes/
|
||||||
|
│ ├── light.qss # 浅色主题样式
|
||||||
|
│ └── dark.qss # 深色主题样式
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
## 构建与运行
|
## 构建与运行
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
|
#include <QFile>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
@@ -19,84 +20,22 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QStatusBar>
|
#include <QStatusBar>
|
||||||
|
|
||||||
// ---- QSS:按按键功能分色,ID 选择器精确定位(widget.ui 里已设好 objectName)----
|
// ---- QSS:从外部 .qss 资源文件加载(themes/light.qss / themes/dark.qss)----
|
||||||
// 教学要点:QSS 选择器认的是 objectName——setObjectName("btnEquals") 之后才能用
|
// 教学要点(QSS 最佳实践):把样式抽成独立 .qss 文件用 QFile 读取,而非硬编码
|
||||||
// #btnEquals{...}。本例的 objectName 在 widget.ui 里已定义,无需再 setObjectName。
|
// C++ 字符串——① 样式与逻辑彻底分离(设计师可独立编辑 .qss);② 开发期改 .qss
|
||||||
|
// 重跑即生效,不必改 C++ 重编译;③ 多主题物理分文件,编辑器对 .qss 有语法高亮。
|
||||||
|
// 资源路径 :/themes/*.qss 由 themes.qrc(AUTORCC)编译进可执行文件。
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// 浅色主题:数字=浅蓝、运算=橙、等号=绿、括号=灰、清空/退格=红、显示区=米黄
|
// 从嵌入资源读取样式表。读取失败返回空串(setStyleSheet 空串即清空样式)。
|
||||||
const char* kLightQss = R"(
|
QString loadQss(const QString& resourcePath) {
|
||||||
QMainWindow { background: #f5f5f5; }
|
QFile f(resourcePath);
|
||||||
QLineEdit#display, QTextEdit#expressionEdit {
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
background: #fffbe6; color: #222;
|
qWarning() << "无法打开样式资源" << resourcePath;
|
||||||
border: 1px solid #d0c8a0; border-radius: 4px;
|
return {};
|
||||||
font-size: 18px; padding: 4px;
|
}
|
||||||
|
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
|
} // namespace
|
||||||
|
|
||||||
@@ -168,7 +107,9 @@ void MainWindow::refreshStatus(const QString& msg) {
|
|||||||
// 避免和未来某控件局部样式打架(Day3 下午坑卡:setStyleSheet 级联失控)。
|
// 避免和未来某控件局部样式打架(Day3 下午坑卡:setStyleSheet 级联失控)。
|
||||||
void MainWindow::applyStyle(bool dark) {
|
void MainWindow::applyStyle(bool dark) {
|
||||||
m_dark = 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) {
|
void MainWindow::onToggleTheme(bool dark) {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>themes/light.qss</file>
|
||||||
|
<file>themes/dark.qss</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
* 深色主题样式表 —— qt_calculator_mainwindow_qss(Day3 下午)
|
||||||
|
* 整体深底浅字,功能分色保留但调暗,便于和浅色对照验证「切换生效」。
|
||||||
|
* 资源路径 :/themes/dark.qss(themes.qrc 嵌入)。
|
||||||
|
* ============================================================================ */
|
||||||
|
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; }
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
* 浅色主题样式表 —— qt_calculator_mainwindow_qss(Day3 下午)
|
||||||
|
* 按按键功能分色:数字=浅蓝、运算=橙、等号=绿、括号=灰、清空/退格=红、显示区=米黄。
|
||||||
|
* 选择器认 objectName(btn0/btnEquals/… 在 widget.ui 定义)。
|
||||||
|
* 由 themes.qrc 嵌入为资源 :/themes/light.qss;mainwindow.cpp 用 QFile 读取后
|
||||||
|
* qApp->setStyleSheet 全局应用——把样式与 C++ 逻辑分离,便于独立编辑、热重载。
|
||||||
|
* ============================================================================ */
|
||||||
|
QMainWindow { background: #f5f5f5; }
|
||||||
|
QLineEdit#display, QTextEdit#expressionEdit {
|
||||||
|
background: #fffbe6; color: #222;
|
||||||
|
border: 1px solid #d0c8a0; border-radius: 4px;
|
||||||
|
font-size: 18px; padding: 4px;
|
||||||
|
}
|
||||||
|
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; }
|
||||||
Reference in New Issue
Block a user