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>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Day3 下午参考实现:计算器 QMainWindow + QSS 双主题
|
||||
|
||||
> 对应教案 [`OUTLINE_4DAY.md`](../../OUTLINE_4DAY.md) Day3 下午「布局/控件 + QSS 美化」
|
||||
> + [`ASSIGNMENTS.md`](../../ASSIGNMENTS.md) Day3 下午作业。手工维护,非生成产物。
|
||||
|
||||
## 定位:上午版 + QSS 美化
|
||||
|
||||
以 [`../calculator_mainwindow/`](../calculator_mainwindow/README.md)(Day3 上午版)为
|
||||
起点,**只加 QSS**:按键按功能分色、`:hover`/`:pressed` 视觉反馈、深/浅双主题切换。
|
||||
菜单/停靠窗/状态栏/计算逻辑与上午版完全一致——**QSS 与逻辑正交**,这正是 Day3 下午
|
||||
「美化」环节要传达的核心。
|
||||
|
||||
## 与上午版的差异(集中在 `mainwindow.{h,cpp}`)
|
||||
|
||||
| 改动 | 位置 | 内容 |
|
||||
|---|---|---|
|
||||
| 双主题 QSS 字符串 | `mainwindow.cpp` 顶部 `kLightQss` / `kDarkQss` | 按键分色 + 伪状态 |
|
||||
| 视图菜单「深色主题」 | `buildMenu()` | `checkable` QAction,`toggled` → `onToggleTheme` |
|
||||
| `applyStyle(bool dark)` | `mainwindow.cpp` | `qApp->setStyleSheet(...)` 全局切换 |
|
||||
| 自测加主题断言 | `runSelfTest()` | 验证切换后 `styleSheet()` 含对应主题标识 |
|
||||
|
||||
`calculatordesigner.{h,cpp}` / `expressionevaluator.{h,cpp}` / `widget.ui` 与上午版
|
||||
完全一致(自包含复制)。
|
||||
|
||||
## QSS 教学要点(对应 OUTLINE_4DAY.md Day3 下午)
|
||||
|
||||
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 与逻辑正交的可维护性演示。
|
||||
|
||||
## 构建与运行
|
||||
|
||||
```bash
|
||||
cmake --build build --target qt_calculator_mainwindow_qss
|
||||
|
||||
# 离屏自测(计算用例 + 主题切换改变 styleSheet 的断言,退出码 0 即全过)
|
||||
PATH=E:/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \
|
||||
QT_QPA_PLATFORM=offscreen \
|
||||
./build/docs/teaching/examples/qt_calculator_mainwindow_qss.exe; echo "exit=$?"
|
||||
|
||||
# 有 GUI 环境:勾选「视图→深色主题」看整体配色切换
|
||||
./build/docs/teaching/examples/qt_calculator_mainwindow_qss.exe
|
||||
```
|
||||
|
||||
## 验收对照(ASSIGNMENTS.md Day3 下午)
|
||||
|
||||
- **基础版**:数字/运算/等号 3 色区分 ✓(本例 5 色,含括号/清空退格)
|
||||
- **基础版**:布局随窗口缩放不重叠 ✓(`QGridLayout` + `QMainWindow` 区域协议)
|
||||
- **挑战版**:`:hover`/`:pressed` ✓
|
||||
- **挑战版**:结果显示框套样式 ✓(`display`/`expressionEdit` 米黄背景 + 圆角)
|
||||
- **挑战版**:深/浅双主题切换 ✓(视图菜单「深色主题」)
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "calculatordesigner.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "expressionevaluator.h"
|
||||
#include "ui_widget.h"
|
||||
|
||||
namespace {
|
||||
bool isOperatorChar(QChar ch) {
|
||||
return ch == QLatin1Char('+') || ch == QLatin1Char('-') ||
|
||||
ch == QLatin1Char('*') || ch == QLatin1Char('/');
|
||||
}
|
||||
|
||||
bool isDigitOrDotChar(QChar ch) {
|
||||
return ch.isDigit() || ch == QLatin1Char('.');
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CalculatorDesigner::CalculatorDesigner(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::CalculatorForm) {
|
||||
ui->setupUi(this); // 表单里的控件树 + QGridLayout 全部由 Designer XML 建好
|
||||
|
||||
connect(ui->btn0, &QPushButton::clicked, this, [this] { onDigit(0); });
|
||||
connect(ui->btn1, &QPushButton::clicked, this, [this] { onDigit(1); });
|
||||
connect(ui->btn2, &QPushButton::clicked, this, [this] { onDigit(2); });
|
||||
connect(ui->btn3, &QPushButton::clicked, this, [this] { onDigit(3); });
|
||||
connect(ui->btn4, &QPushButton::clicked, this, [this] { onDigit(4); });
|
||||
connect(ui->btn5, &QPushButton::clicked, this, [this] { onDigit(5); });
|
||||
connect(ui->btn6, &QPushButton::clicked, this, [this] { onDigit(6); });
|
||||
connect(ui->btn7, &QPushButton::clicked, this, [this] { onDigit(7); });
|
||||
connect(ui->btn8, &QPushButton::clicked, this, [this] { onDigit(8); });
|
||||
connect(ui->btn9, &QPushButton::clicked, this, [this] { onDigit(9); });
|
||||
connect(ui->btnDot, &QPushButton::clicked, this, [this] { onDot(); });
|
||||
|
||||
connect(ui->btnPlus, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('+')); });
|
||||
connect(ui->btnMinus, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('-')); });
|
||||
connect(ui->btnMul, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('*')); });
|
||||
connect(ui->btnDiv, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('/')); });
|
||||
|
||||
connect(ui->btnLeftParen, &QPushButton::clicked, this, &CalculatorDesigner::onLeftParen);
|
||||
connect(ui->btnRightParen, &QPushButton::clicked, this, &CalculatorDesigner::onRightParen);
|
||||
|
||||
connect(ui->btnEquals, &QPushButton::clicked, this, &CalculatorDesigner::onEquals);
|
||||
connect(ui->btnClear, &QPushButton::clicked, this, &CalculatorDesigner::onClear);
|
||||
connect(ui->btnBackspace, &QPushButton::clicked, this, &CalculatorDesigner::onBackspace);
|
||||
|
||||
onClear();
|
||||
}
|
||||
|
||||
CalculatorDesigner::~CalculatorDesigner() { delete ui; }
|
||||
|
||||
void CalculatorDesigner::onDigit(int digit) {
|
||||
if (m_error) onClear();
|
||||
if (m_resultShown) {
|
||||
m_expression.clear();
|
||||
m_openParenCount = 0;
|
||||
m_resultShown = false;
|
||||
}
|
||||
|
||||
const QString token = currentOperandToken();
|
||||
if (token == QStringLiteral("0")) {
|
||||
m_expression.chop(1); // 用新数字替换孤立的前导 0,避免出现 "007"
|
||||
} else if (currentOperandDigitCount() >= kMaxOperandLength) {
|
||||
return; // 达到单个运算数的长度上限,忽略后续数字
|
||||
}
|
||||
if (m_expression.size() >= kMaxExpressionLength) return;
|
||||
|
||||
m_expression.append(QString::number(digit));
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onDot() {
|
||||
if (m_error) onClear();
|
||||
if (m_resultShown) {
|
||||
m_expression.clear();
|
||||
m_openParenCount = 0;
|
||||
m_resultShown = false;
|
||||
}
|
||||
|
||||
const QString token = currentOperandToken();
|
||||
if (token.contains(QLatin1Char('.'))) return; // 当前运算数已有小数点
|
||||
|
||||
const int appended = token.isEmpty() ? 2 : 1; // 空token时需要补一个前导 0
|
||||
if (m_expression.size() + appended > kMaxExpressionLength) return;
|
||||
|
||||
if (token.isEmpty()) m_expression.append(QLatin1Char('0'));
|
||||
m_expression.append(QLatin1Char('.'));
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onOperator(QChar op) {
|
||||
if (m_error) return;
|
||||
if (m_resultShown) {
|
||||
m_expression = m_lastResultText;
|
||||
m_resultShown = false;
|
||||
}
|
||||
|
||||
if (m_expression.isEmpty()) {
|
||||
if (op != QLatin1Char('-')) return; // 表达式开头只允许负号(单目负号)
|
||||
} else {
|
||||
const QChar last = m_expression.at(m_expression.size() - 1);
|
||||
if (last == QLatin1Char('(')) {
|
||||
if (op != QLatin1Char('-')) return; // 左括号后只允许负号
|
||||
} else if (isOperatorChar(last)) {
|
||||
m_expression.chop(1); // 连续按运算符时,用新的运算符替换上一个
|
||||
}
|
||||
}
|
||||
|
||||
if (m_expression.size() >= kMaxExpressionLength) return;
|
||||
m_expression.append(op);
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onLeftParen() {
|
||||
if (m_error) onClear();
|
||||
if (m_resultShown) {
|
||||
m_expression.clear();
|
||||
m_openParenCount = 0;
|
||||
m_resultShown = false;
|
||||
}
|
||||
|
||||
if (!m_expression.isEmpty()) {
|
||||
const QChar last = m_expression.at(m_expression.size() - 1);
|
||||
if (!isOperatorChar(last) && last != QLatin1Char('(')) {
|
||||
return; // 数字或右括号后不支持隐式乘法,忽略
|
||||
}
|
||||
}
|
||||
if (m_expression.size() >= kMaxExpressionLength) return;
|
||||
|
||||
m_expression.append(QLatin1Char('('));
|
||||
++m_openParenCount;
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onRightParen() {
|
||||
if (m_error) return;
|
||||
if (m_resultShown) return;
|
||||
if (m_openParenCount <= 0 || m_expression.isEmpty()) return;
|
||||
|
||||
const QChar last = m_expression.at(m_expression.size() - 1);
|
||||
if (isOperatorChar(last) || last == QLatin1Char('(')) return; // 括号内需要完整子表达式
|
||||
if (m_expression.size() >= kMaxExpressionLength) return;
|
||||
|
||||
m_expression.append(QLatin1Char(')'));
|
||||
--m_openParenCount;
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onEquals() {
|
||||
if (m_error) return;
|
||||
if (m_expression.isEmpty()) return;
|
||||
|
||||
QString toEvaluate = m_expression;
|
||||
for (int i = 0; i < m_openParenCount; ++i) toEvaluate.append(QLatin1Char(')')); // 自动补全未闭合括号
|
||||
|
||||
const ExpressionEvaluator::Result result = ExpressionEvaluator::evaluate(toEvaluate);
|
||||
if (!result.ok) {
|
||||
showError();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString resultText = QString::number(result.value, 'g', ExpressionEvaluator::kResultPrecision);
|
||||
m_expression = toEvaluate;
|
||||
m_lastResultText = resultText;
|
||||
m_openParenCount = 0;
|
||||
m_resultShown = true;
|
||||
ui->expressionEdit->setPlainText(m_expression + QStringLiteral(" = ") + resultText);
|
||||
ui->display->setText(resultText);
|
||||
|
||||
// 【新增】通知外壳(历史停靠窗):本次求值的表达式与结果。
|
||||
emit evaluated(m_expression, resultText);
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onClear() {
|
||||
m_expression.clear();
|
||||
m_lastResultText.clear();
|
||||
m_openParenCount = 0;
|
||||
m_resultShown = false;
|
||||
m_error = false;
|
||||
ui->expressionEdit->clear();
|
||||
ui->display->setText(QStringLiteral("0"));
|
||||
}
|
||||
|
||||
void CalculatorDesigner::onBackspace() {
|
||||
if (m_error) { onClear(); return; }
|
||||
if (m_resultShown) return; // 结果已展示,需先清空或继续输入新表达式
|
||||
if (m_expression.isEmpty()) return;
|
||||
|
||||
const QChar removed = m_expression.at(m_expression.size() - 1);
|
||||
m_expression.chop(1);
|
||||
if (removed == QLatin1Char('(')) --m_openParenCount;
|
||||
else if (removed == QLatin1Char(')')) ++m_openParenCount;
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
QString CalculatorDesigner::displayText() const { return ui->display->text(); }
|
||||
|
||||
QString CalculatorDesigner::expressionText() const { return ui->expressionEdit->toPlainText(); }
|
||||
|
||||
// 【新增】把一条历史表达式回填到编辑区:重置状态机并把表达式原样载入,
|
||||
// 未闭合括号数按字符重新统计。调用后用户可直接按 = 重新求值,或继续编辑。
|
||||
void CalculatorDesigner::loadExpression(const QString& expr) {
|
||||
if (m_error) onClear();
|
||||
m_expression = expr;
|
||||
m_openParenCount = 0;
|
||||
for (const QChar ch : m_expression) {
|
||||
if (ch == QLatin1Char('(')) ++m_openParenCount;
|
||||
else if (ch == QLatin1Char(')')) --m_openParenCount;
|
||||
}
|
||||
if (m_openParenCount < 0) m_openParenCount = 0;
|
||||
m_resultShown = false;
|
||||
m_lastResultText.clear();
|
||||
refreshDisplays();
|
||||
}
|
||||
|
||||
QString CalculatorDesigner::currentOperandToken() const {
|
||||
int start = m_expression.size();
|
||||
while (start > 0 && isDigitOrDotChar(m_expression.at(start - 1))) --start;
|
||||
return m_expression.mid(start);
|
||||
}
|
||||
|
||||
int CalculatorDesigner::currentOperandDigitCount() const {
|
||||
const QString token = currentOperandToken();
|
||||
int count = 0;
|
||||
for (const QChar ch : token) {
|
||||
if (ch.isDigit()) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
QString CalculatorDesigner::lastNumberToken() const {
|
||||
int end = m_expression.size();
|
||||
while (end > 0 && !isDigitOrDotChar(m_expression.at(end - 1))) --end; // 跳过末尾的运算符/括号
|
||||
int start = end;
|
||||
while (start > 0 && isDigitOrDotChar(m_expression.at(start - 1))) --start;
|
||||
return m_expression.mid(start, end - start);
|
||||
}
|
||||
|
||||
void CalculatorDesigner::refreshDisplays() {
|
||||
ui->expressionEdit->setPlainText(m_expression);
|
||||
const QString token = lastNumberToken();
|
||||
ui->display->setText(token.isEmpty() ? QStringLiteral("0") : token);
|
||||
}
|
||||
|
||||
void CalculatorDesigner::showError() {
|
||||
ui->display->setText(QStringLiteral("Error"));
|
||||
m_error = true;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <QChar>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class CalculatorForm;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
// CalculatorDesigner —— 计算器面板(QWidget)。
|
||||
// 【Day3 calculator_mainwindow】相对 ../calculator_designer_ext/ 版仅两处改动:
|
||||
// ① 新增 evaluated(expression,result) 信号:按 = 求值成功后发出,供 MainWindow
|
||||
// 的历史停靠窗订阅(演示「给已有部件加信号,与外壳通信」)。
|
||||
// ② 新增 loadExpression(expr):把一条历史表达式回填到编辑区(演示「部件对外
|
||||
// 提供 API」,配合历史双击回填)。
|
||||
// 其余输入校验/状态机/求值逻辑一字未改。
|
||||
class CalculatorDesigner : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CalculatorDesigner(QWidget* parent = nullptr);
|
||||
~CalculatorDesigner() override;
|
||||
|
||||
// 供自测直接调用,效果等价于点击对应按钮;逻辑与 ../calculator_designer_ext/ 版一字不差。
|
||||
void onDigit(int digit);
|
||||
void onDot();
|
||||
void onOperator(QChar op);
|
||||
void onLeftParen();
|
||||
void onRightParen();
|
||||
void onEquals();
|
||||
void onClear();
|
||||
void onBackspace();
|
||||
|
||||
QString displayText() const;
|
||||
QString expressionText() const;
|
||||
|
||||
// 【新增】把一条历史表达式回填到编辑区(历史双击时调用)。
|
||||
void loadExpression(const QString& expr);
|
||||
|
||||
signals:
|
||||
// 【新增】按 = 求值成功后发出,参数为(自动补全括号后的表达式, 结果文本)。
|
||||
void evaluated(const QString& expression, const QString& result);
|
||||
|
||||
private:
|
||||
// 单个运算数(数字串)允许的最大长度,要求不得小于 10 位。
|
||||
static constexpr int kMaxOperandLength = 15;
|
||||
static_assert(kMaxOperandLength >= 10, "单个运算数的最大长度不得小于 10 位");
|
||||
// 表达式总长度上限,要求不得低于 100 个字符。
|
||||
static constexpr int kMaxExpressionLength = 200;
|
||||
static_assert(kMaxExpressionLength >= 100, "表达式总长度上限不得低于 100 个字符");
|
||||
|
||||
QString currentOperandToken() const;
|
||||
int currentOperandDigitCount() const;
|
||||
QString lastNumberToken() const;
|
||||
void refreshDisplays();
|
||||
void showError();
|
||||
|
||||
Ui::CalculatorForm* ui;
|
||||
QString m_expression; // 当前正在编辑的表达式,例如 "12+3*(4-2"
|
||||
QString m_lastResultText; // 上一次成功计算得到的结果文本
|
||||
int m_openParenCount = 0; // 尚未闭合的左括号数量
|
||||
bool m_resultShown = false; // 显示区当前展示的是计算结果而非正在输入的数字
|
||||
bool m_error = false;
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "expressionevaluator.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// 递归下降解析器:expression := term (('+'|'-') term)*
|
||||
// term := factor (('*'|'/') factor)*
|
||||
// factor := ('+'|'-') factor | '(' expression ')' | number
|
||||
// 括号会先被完整求值,天然实现"优先级提升";乘除法在 term 层处理,
|
||||
// 天然高于 expression 层的加减法。
|
||||
// 【Day3 calculator_mainwindow】与 ../calculator_designer_ext/ 版一字不差。
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(const QString& text) : m_text(text) {}
|
||||
|
||||
ExpressionEvaluator::Result parse() {
|
||||
ExpressionEvaluator::Result result;
|
||||
skipSpaces();
|
||||
if (atEnd()) {
|
||||
result.errorMessage = QStringLiteral("表达式为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
const double value = parseExpression(ok);
|
||||
skipSpaces();
|
||||
if (!ok) {
|
||||
result.errorMessage = m_error;
|
||||
return result;
|
||||
}
|
||||
if (!atEnd()) {
|
||||
result.errorMessage = QStringLiteral("表达式包含无法识别的字符");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.ok = true;
|
||||
result.value = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
double parseExpression(bool& ok) {
|
||||
double value = parseTerm(ok);
|
||||
while (ok) {
|
||||
skipSpaces();
|
||||
if (peek() == QLatin1Char('+')) {
|
||||
advance();
|
||||
value += parseTerm(ok);
|
||||
} else if (peek() == QLatin1Char('-')) {
|
||||
advance();
|
||||
value -= parseTerm(ok);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseTerm(bool& ok) {
|
||||
double value = parseFactor(ok);
|
||||
while (ok) {
|
||||
skipSpaces();
|
||||
if (peek() == QLatin1Char('*')) {
|
||||
advance();
|
||||
value *= parseFactor(ok);
|
||||
} else if (peek() == QLatin1Char('/')) {
|
||||
advance();
|
||||
const double rhs = parseFactor(ok);
|
||||
if (!ok) return 0.0;
|
||||
if (rhs == 0.0) {
|
||||
fail(QStringLiteral("除数不能为零"), ok);
|
||||
return 0.0;
|
||||
}
|
||||
value /= rhs;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseFactor(bool& ok) {
|
||||
skipSpaces();
|
||||
if (peek() == QLatin1Char('+')) {
|
||||
advance();
|
||||
return parseFactor(ok);
|
||||
}
|
||||
if (peek() == QLatin1Char('-')) {
|
||||
advance();
|
||||
return -parseFactor(ok);
|
||||
}
|
||||
if (peek() == QLatin1Char('(')) {
|
||||
advance();
|
||||
const double value = parseExpression(ok);
|
||||
if (!ok) return 0.0;
|
||||
skipSpaces();
|
||||
if (peek() != QLatin1Char(')')) {
|
||||
fail(QStringLiteral("括号不匹配"), ok);
|
||||
return 0.0;
|
||||
}
|
||||
advance();
|
||||
return value;
|
||||
}
|
||||
return parseNumber(ok);
|
||||
}
|
||||
|
||||
double parseNumber(bool& ok) {
|
||||
skipSpaces();
|
||||
const int start = m_pos;
|
||||
bool hasDigits = false;
|
||||
while (!atEnd() && peek().isDigit()) { advance(); hasDigits = true; }
|
||||
if (!atEnd() && peek() == QLatin1Char('.')) {
|
||||
advance();
|
||||
while (!atEnd() && peek().isDigit()) { advance(); hasDigits = true; }
|
||||
}
|
||||
if (!hasDigits) {
|
||||
fail(QStringLiteral("表达式格式错误,缺少数字"), ok);
|
||||
return 0.0;
|
||||
}
|
||||
const QString token = m_text.mid(start, m_pos - start);
|
||||
bool numOk = false;
|
||||
const double value = token.toDouble(&numOk);
|
||||
if (!numOk) {
|
||||
fail(QStringLiteral("数字格式错误"), ok);
|
||||
return 0.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void skipSpaces() { while (!atEnd() && peek().isSpace()) advance(); }
|
||||
bool atEnd() const { return m_pos >= m_text.size(); }
|
||||
QChar peek() const { return atEnd() ? QChar() : m_text.at(m_pos); }
|
||||
void advance() { ++m_pos; }
|
||||
void fail(const QString& message, bool& ok) { ok = false; m_error = message; }
|
||||
|
||||
const QString& m_text;
|
||||
int m_pos = 0;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ExpressionEvaluator::Result ExpressionEvaluator::evaluate(const QString& expression) {
|
||||
Parser parser(expression);
|
||||
return parser.parse();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
// 对形如 "1+2*(3-4)" 的中缀表达式求值:
|
||||
// 乘除优先级高于加减,括号可提升优先级。
|
||||
// 【Day3 calculator_mainwindow】与 ../calculator_designer_ext/ 版一字不差,
|
||||
// 求值逻辑独立于界面外壳——Day3 把 QWidget 换成 QMainWindow,本类不动。
|
||||
class ExpressionEvaluator {
|
||||
public:
|
||||
struct Result {
|
||||
bool ok = false;
|
||||
double value = 0.0;
|
||||
QString errorMessage;
|
||||
};
|
||||
|
||||
// 结果显示精度(有效数字位数),要求不得小于 10。
|
||||
static constexpr int kResultPrecision = 12;
|
||||
static_assert(kResultPrecision >= 10, "结果显示精度不得小于 10 位有效数字");
|
||||
|
||||
static Result evaluate(const QString& expression);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// ============================================================================
|
||||
// calculator_mainwindow_qss —— Day3 下午版程序入口(上午版 + QSS 双主题)
|
||||
// 对应教案 OUTLINE_4DAY.md Day3 下午「布局/控件 + QSS 美化」。
|
||||
// 与 ../calculator_mainwindow/main.cpp 仅 include 的头不同(本目录自己的),
|
||||
// 逻辑一致:offscreen 时调 MainWindow::runSelfTest(),按返回值决定退出码。
|
||||
// ============================================================================
|
||||
#include <QApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
|
||||
QTimer::singleShot(0, &w, [&w] {
|
||||
const bool ok = w.runSelfTest();
|
||||
QCoreApplication::exit(ok ? 0 : 1);
|
||||
});
|
||||
}
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// ============================================================================
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// ============================================================================
|
||||
// mainwindow.h —— Day3 下午版:上午版 + QSS 界面美化(双主题)
|
||||
//
|
||||
// 相对 ../calculator_mainwindow/(上午版)的改动:
|
||||
// ① 视图菜单新增「深色主题」可勾选项;
|
||||
// ② applyStyle(bool dark) 按主题切换 setStyleSheet;
|
||||
// ③ runSelfTest() 增加「主题切换确实改变 styleSheet」的断言。
|
||||
// 计算器面板、菜单/停靠窗/状态栏结构与上午版完全一致——QSS 与逻辑正交,
|
||||
// 正是 Day3 下午「美化」环节要传达的核心点。
|
||||
// ============================================================================
|
||||
#ifndef CALCULATOR_MAINWINDOW_QSS_H
|
||||
#define CALCULATOR_MAINWINDOW_QSS_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class CalculatorDesigner;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QDockWidget;
|
||||
class QAction;
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget* parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
bool runSelfTest();
|
||||
|
||||
private slots:
|
||||
void onAbout();
|
||||
void onClearHistory();
|
||||
void onToggleHistoryVisible(bool checked);
|
||||
void onToggleTheme(bool dark); // 【下午版新增】深/浅主题切换
|
||||
void onEvaluated(const QString& expr, const QString& result);
|
||||
void onHistoryDoubleClicked(QListWidgetItem* item);
|
||||
|
||||
private:
|
||||
void buildMenu();
|
||||
void buildDock();
|
||||
void buildStatusBar();
|
||||
void refreshStatus(const QString& msg);
|
||||
void applyStyle(bool dark); // 【下午版新增】
|
||||
|
||||
CalculatorDesigner* m_calc = nullptr;
|
||||
QListWidget* m_history = nullptr;
|
||||
QDockWidget* m_dock = nullptr;
|
||||
QAction* m_actQuit = nullptr;
|
||||
QAction* m_actClearHistory = nullptr;
|
||||
QAction* m_actToggleHistory = nullptr;
|
||||
QAction* m_actToggleTheme = nullptr; // 【下午版新增】
|
||||
QAction* m_actAbout = nullptr;
|
||||
bool m_dark = false; // 【下午版新增】当前是否深色主题
|
||||
};
|
||||
|
||||
#endif // CALCULATOR_MAINWINDOW_QSS_H
|
||||
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalculatorForm</class>
|
||||
<widget class="QWidget" name="CalculatorForm">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>358</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>计算器面板</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="rootLayout">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="expressionEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="display">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="keypadLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="btn7">
|
||||
<property name="text">
|
||||
<string>7</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="btn8">
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="btn9">
|
||||
<property name="text">
|
||||
<string>9</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="btnDiv">
|
||||
<property name="text">
|
||||
<string>÷</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="btn4">
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="btn5">
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="btn6">
|
||||
<property name="text">
|
||||
<string>6</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="btnMul">
|
||||
<property name="text">
|
||||
<string>×</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="btn1">
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="btn2">
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="btn3">
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="btnMinus">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="btn0">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="btnDot">
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="btnEquals">
|
||||
<property name="text">
|
||||
<string>=</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QPushButton" name="btnPlus">
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QPushButton" name="btnClear">
|
||||
<property name="text">
|
||||
<string>C</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="btnBackspace">
|
||||
<property name="text">
|
||||
<string>⌫</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="btnLeftParen">
|
||||
<property name="text">
|
||||
<string>(</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="btnRightParen">
|
||||
<property name="text">
|
||||
<string>)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user