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
@@ -5,38 +5,55 @@
## 定位:上午版 + QSS 美化
以 [`../calculator_mainwindow/`](../calculator_mainwindow/README.md)Day3 上午版)为
起点,**只加 QSS**:按键按功能分色、`:hover`/`:pressed` 视觉反馈、深/浅双主题切换。
以 [`../calculator_mainwindow/`](../calculator_mainwindow/README.md)Day3 上午版)为起点,
**只加 QSS**:按键按功能分色、`:hover`/`:pressed` 视觉反馈、深/浅双主题切换。
菜单/停靠窗/状态栏/计算逻辑与上午版完全一致——**QSS 与逻辑正交**,这正是 Day3 下午
「美化」环节要传达的核心。
## 与上午版的差异(集中在 `mainwindow.{h,cpp}`
## 与上午版的差异(集中在 `mainwindow.{h,cpp}` + `themes/`
| 改动 | 位置 | 内容 |
|---|---|---|
| 双主题 QSS 字符串 | `mainwindow.cpp` 顶部 `kLightQss` / `kDarkQss` | 按键分色 + 伪状态 |
| 视图菜单「深色主题」 | `buildMenu()` | `checkable` QAction`toggled``onToggleTheme` |
| `applyStyle(bool dark)` | `mainwindow.cpp` | `qApp->setStyleSheet(...)` 全局切换 |
| 自测加主题断言 | `runSelfTest()` | 验证切换后 `styleSheet()` 含对应主题标识 |
| 双主题样式表(**外部 `.qss` 文件** | `themes/light.qss``themes/dark.qss` | 按键分色 + 伪状态 |
| 资源清单 | `themes.qrc``AUTORCC` | 嵌入 `:/themes/light.qss``:/themes/dark.qss` |
| 视图菜单「深色主题」 | `buildMenu()` | `checkable` QAction`toggled → onToggleTheme` |
| `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 { ... }` 命中的前提是控件
`setObjectName("btnEquals")`。本例的 objectName 在 `widget.ui` 里已定义
`<widget class="QPushButton" name="btnEquals">`),无需再 `setObjectName`——
这是「样式写了没生效」头号原因(忘设 objectName)的反面教材。
2. **按键分色用功能归类**:数字键(`btn0``btn9`,`btnDot`)浅蓝、运算键
`btnPlus/Minus/Mul/Div`)橙、等号(`btnEquals`)绿、括号灰、清空/退格红——
ID 选择器分组,比给每个按钮单独写样式更易维护。
3. **伪状态**`:hover` / `:pressed` 不用手写鼠标事件就能做交互反馈。
4. **`qApp->setStyleSheet` 全局 vs `widget->setStyleSheet` 局部**:本例用全局,
一次切换全部生效。坑卡:全局样式会被 widget 级样式覆盖,混用易级联失控
OUTLINE_4DAY.md Day3 下午「QSS 级联失控」卡)。
5. **主题切换不碰逻辑**`applyStyle` 只改 `styleSheet`,不动任何业务代码——
QSS 与逻辑正交的可维护性演示
1. **样式抽成外部 `.qss` 文件**(而非硬编码 C++ 字符串):`light.qss` / `dark.qss` 是纯样式,
`themes.qrc` 编译进可执行文件,`mainwindow.cpp``QFile` 读取后 `qApp->setStyleSheet`
应用。好处:
- 设计师/非程序员可直接编辑 `.qss`(不用碰 C++);
- 开发期改 `.qss` 重跑即生效(不必改 C++ 重编译);
- 多主题物理分文件,编辑器对 `.qss` 有语法高亮。
2. **ID 选择器认 objectName**`QPushButton#btnEquals { ... }` 命中的前提是控件
`setObjectName("btnEquals")`——本例 objectName 在 `widget.ui` 定义
`<widget class="QPushButton" name="btnEquals">`)。这是「样式写了没生效」头号原因
(忘设 objectName)的反面教材。
3. **按键分色用功能归类**DRY):数字键(`btn0``btn9`,`btnDot`)浅蓝、运算键橙、等号绿、
括号灰、清空退格红——用 ID 选择器分组,比给每个按钮单独写样式更易维护。
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
```
## 构建与运行