Files
scuc-qt-course/docs/teaching/examples/calculator_mainwindow_qss/mainwindow.cpp
T
张宗平 05a685c952 Day3 教学示例:增强版计算器接入 + QMainWindow/QSS 两版参考实现
- calculator_designer_ext(Day2 增强版:表达式求值 + 括号,递归下降解析器)接入 cmake
- calculator_mainwindow(Day3 上午):增强版计算器装进 QMainWindow,
  菜单/状态栏/历史停靠窗,纯代码 + 显式 connect,与 mainwindow_showcase 形成坑卡对照
- calculator_mainwindow_qss(Day3 下午):上午版 + QSS 双主题美化(按键分色/伪状态/深浅切换)
- 验证 mainwindow_showcase(QMainWindow 六大组件综合演示,.ui + .qrc)
- OUTLINE_4DAY.md / ASSIGNMENTS.md Day3 衔接增强版起点 + 参考实现指引
- 补提交 Day2 calculator/ 与 calculator_designer/ 参考实现(CMakeLists 已引用)
- 全量构建 28/28,4 目标离屏自测退出码 0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:59:05 +08:00

278 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// mainwindow.cpp —— Day3 下午版实现(上午版 + QSS 双主题)
// 与 ../calculator_mainwindow/mainwindow.cpp 的差异全部集中在本文件的 QSS 部分
// (见 kLightQss / kDarkQss / applyStyle / onToggleTheme / 视图菜单多一项),
// 菜单/停靠窗/状态栏/历史/自测的计算用例部分一字未改。
// ============================================================================
#include "mainwindow.h"
#include "calculatordesigner.h"
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QDockWidget>
#include <QKeySequence>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QStatusBar>
// ---- QSS:按按键功能分色,ID 选择器精确定位(widget.ui 里已设好 objectName----
// 教学要点:QSS 选择器认的是 objectName——setObjectName("btnEquals") 之后才能用
// #btnEquals{...}。本例的 objectName 在 widget.ui 里已定义,无需再 setObjectName。
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;
}
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
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
setWindowTitle(QStringLiteral("计算器(QMainWindow + QSS 版)"));
m_calc = new CalculatorDesigner(this);
setCentralWidget(m_calc);
buildMenu();
buildDock();
buildStatusBar();
connect(m_calc, &CalculatorDesigner::evaluated, this, &MainWindow::onEvaluated);
applyStyle(false); // 默认浅色主题
}
MainWindow::~MainWindow() = default;
void MainWindow::buildMenu() {
QMenu* fileMenu = menuBar()->addMenu(QStringLiteral("文件(&F)"));
m_actQuit = fileMenu->addAction(QStringLiteral("退出(&Q)"));
m_actQuit->setShortcut(QKeySequence(QStringLiteral("Ctrl+Q")));
connect(m_actQuit, &QAction::triggered, this, &QWidget::close);
QMenu* editMenu = menuBar()->addMenu(QStringLiteral("编辑(&E)"));
m_actClearHistory = editMenu->addAction(QStringLiteral("清空历史(&C)"));
connect(m_actClearHistory, &QAction::triggered, this, &MainWindow::onClearHistory);
QMenu* viewMenu = menuBar()->addMenu(QStringLiteral("视图(&V)"));
m_actToggleHistory = viewMenu->addAction(QStringLiteral("显示历史(&H)"));
m_actToggleHistory->setCheckable(true);
m_actToggleHistory->setChecked(true);
connect(m_actToggleHistory, &QAction::toggled, this, &MainWindow::onToggleHistoryVisible);
// 【下午版新增】深色主题切换:checkable,勾上=深色
m_actToggleTheme = viewMenu->addAction(QStringLiteral("深色主题(&D)"));
m_actToggleTheme->setCheckable(true);
m_actToggleTheme->setChecked(false);
connect(m_actToggleTheme, &QAction::toggled, this, &MainWindow::onToggleTheme);
QMenu* helpMenu = menuBar()->addMenu(QStringLiteral("帮助(&H)"));
m_actAbout = helpMenu->addAction(QStringLiteral("关于(&A)"));
connect(m_actAbout, &QAction::triggered, this, &MainWindow::onAbout);
}
void MainWindow::buildDock() {
m_history = new QListWidget(this);
m_history->setAlternatingRowColors(true);
m_dock = new QDockWidget(QStringLiteral("历史"), this);
m_dock->setObjectName(QStringLiteral("historyDock"));
m_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea);
m_dock->setWidget(m_history);
addDockWidget(Qt::RightDockWidgetArea, m_dock);
connect(m_history, &QListWidget::itemDoubleClicked, this, &MainWindow::onHistoryDoubleClicked);
}
void MainWindow::buildStatusBar() {
statusBar()->showMessage(QStringLiteral("就绪"));
}
void MainWindow::refreshStatus(const QString& msg) {
statusBar()->showMessage(msg, 3000);
}
// 【下午版新增】主题切换:用 qApp->setStyleSheet 全局生效(影响所有窗口及其子部件)。
// 注意 QSS 级联——qApp 级样式会被 widget 级 setStyleSheet 覆盖;本例只用 qApp 级,
// 避免和未来某控件局部样式打架(Day3 下午坑卡:setStyleSheet 级联失控)。
void MainWindow::applyStyle(bool dark) {
m_dark = dark;
qApp->setStyleSheet(QString::fromUtf8(dark ? kDarkQss : kLightQss));
}
void MainWindow::onToggleTheme(bool dark) {
applyStyle(dark);
refreshStatus(dark ? QStringLiteral("已切换深色主题") : QStringLiteral("已切换浅色主题"));
}
void MainWindow::onAbout() {
QMessageBox::about(this, QStringLiteral("关于"),
QStringLiteral(
"<b>计算器(QMainWindow + QSS 版)</b><br><br>"
"Day3 下午教学参考实现:在上午版基础上加 QSS 双主题美化——"
"数字/运算/等号/括号/清空退格按键分色,:hover/:pressed 视觉反馈,"
"深/浅主题切换。<br><br>"
"对应教案 OUTLINE_4DAY.md Day3 下午。"));
}
void MainWindow::onClearHistory() {
m_history->clear();
refreshStatus(QStringLiteral("已清空历史"));
}
void MainWindow::onToggleHistoryVisible(bool checked) {
m_dock->setVisible(checked);
}
void MainWindow::onEvaluated(const QString& expr, const QString& result) {
m_history->addItem(expr + QStringLiteral(" = ") + result);
refreshStatus(QStringLiteral("结果: ") + result);
}
void MainWindow::onHistoryDoubleClicked(QListWidgetItem* item) {
const QString text = item->text();
const int idx = text.lastIndexOf(QStringLiteral(" = "));
if (idx < 0) return;
m_calc->loadExpression(text.left(idx));
refreshStatus(QStringLiteral("已回填表达式"));
}
// ------------------------------------------------------------ 自测 ----
bool MainWindow::runSelfTest() {
auto expect = [](const char* label, bool cond) -> bool {
if (!cond) { qCritical() << "自测失败:" << label; return false; }
qInfo() << "自测通过:" << label; return true;
};
auto expectEq = [](const char* label, const QString& actual, const QString& expected) -> bool {
if (actual != expected) {
qCritical() << "自测失败:" << label << "实际=" << actual << "预期=" << expected;
return false;
}
qInfo() << "自测通过:" << label << "=>" << actual;
return true;
};
// —— 计算用例与上午版一致(验证加 QSS 后行为不变)——
m_calc->onClear();
m_calc->onDigit(7); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(3); m_calc->onEquals();
if (!expectEq("7 + 3 =", m_calc->displayText(), QStringLiteral("10"))) return false;
if (!expect("历史追加第 1 条", m_history->count() == 1)) return false;
m_calc->onClear();
m_calc->onDigit(6); m_calc->onOperator(QLatin1Char('/')); m_calc->onDigit(0); m_calc->onEquals();
if (!expectEq("6 ÷ 0 =", m_calc->displayText(), QStringLiteral("Error"))) return false;
if (!expect("除零 Error 不进历史", m_history->count() == 1)) return false;
m_calc->onClear();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('+'));
m_calc->onDigit(3); m_calc->onOperator(QLatin1Char('*'));
m_calc->onDigit(4); m_calc->onEquals();
if (!expectEq("2 + 3 × 4 =", m_calc->displayText(), QStringLiteral("14"))) return false;
m_calc->onClear();
m_calc->onLeftParen();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(3);
m_calc->onRightParen();
m_calc->onOperator(QLatin1Char('*'));
m_calc->onDigit(4); m_calc->onEquals();
if (!expectEq("(2 + 3) × 4 =", m_calc->displayText(), QStringLiteral("20"))) return false;
// —— 【下午版新增】主题切换确实改变全局 styleSheet ——
applyStyle(true);
if (!expect("深色主题 styleSheet 非空", !qApp->styleSheet().isEmpty())) return false;
if (!expect("深色主题 styleSheet 含深色标识",
qApp->styleSheet().contains(QStringLiteral("#2b2b2b")))) return false;
applyStyle(false);
if (!expect("浅色主题 styleSheet 含浅色标识",
qApp->styleSheet().contains(QStringLiteral("#f5f5f5")))) return false;
if (!expect("主题状态 m_dark 已回浅", !m_dark)) return false;
// —— 历史双击回填 + 清空(与上午版一致)——
m_calc->onClear();
m_calc->onLeftParen();
m_calc->onDigit(1); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(2);
m_calc->onEquals();
QListWidgetItem* last = m_history->item(m_history->count() - 1);
if (!expectEq("最后一条历史", last->text(), QStringLiteral("(1+2) = 3"))) return false;
onHistoryDoubleClicked(last);
if (!expectEq("双击回填表达式区", m_calc->expressionText(), QStringLiteral("(1+2)"))) return false;
onClearHistory();
if (!expect("清空历史后 count==0", m_history->count() == 0)) return false;
if (!expect("关于 action 已创建", m_actAbout != nullptr)) return false;
if (!expect("主题 action 已创建", m_actToggleTheme != nullptr)) return false;
return true;
}