diff --git a/docs/teaching/examples/calculator_mainwindow_qss/README.md b/docs/teaching/examples/calculator_mainwindow_qss/README.md index 1d8e8aa..d41ebc7 100644 --- a/docs/teaching/examples/calculator_mainwindow_qss/README.md +++ b/docs/teaching/examples/calculator_mainwindow_qss/README.md @@ -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` 里已定义 - (``),无需再 `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` 定义 + (``)。这是「样式写了没生效」头号原因 + (忘设 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 +``` ## 构建与运行 diff --git a/docs/teaching/examples/calculator_mainwindow_qss/mainwindow.cpp b/docs/teaching/examples/calculator_mainwindow_qss/mainwindow.cpp index 4d5b4e9..471a1a2 100644 --- a/docs/teaching/examples/calculator_mainwindow_qss/mainwindow.cpp +++ b/docs/teaching/examples/calculator_mainwindow_qss/mainwindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,84 +20,22 @@ #include #include -// ---- 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.qrc(AUTORCC)编译进可执行文件。 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) { diff --git a/docs/teaching/examples/calculator_mainwindow_qss/themes.qrc b/docs/teaching/examples/calculator_mainwindow_qss/themes.qrc new file mode 100644 index 0000000..600d28d --- /dev/null +++ b/docs/teaching/examples/calculator_mainwindow_qss/themes.qrc @@ -0,0 +1,6 @@ + + + themes/light.qss + themes/dark.qss + + diff --git a/docs/teaching/examples/calculator_mainwindow_qss/themes/dark.qss b/docs/teaching/examples/calculator_mainwindow_qss/themes/dark.qss new file mode 100644 index 0000000..a7f0c3d --- /dev/null +++ b/docs/teaching/examples/calculator_mainwindow_qss/themes/dark.qss @@ -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; } diff --git a/docs/teaching/examples/calculator_mainwindow_qss/themes/light.qss b/docs/teaching/examples/calculator_mainwindow_qss/themes/light.qss new file mode 100644 index 0000000..fb2d5db --- /dev/null +++ b/docs/teaching/examples/calculator_mainwindow_qss/themes/light.qss @@ -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; }