qml简易计算器

用 Qt6 + QML 写一个跨平台计算器:EasyCalculator 项目实践

本文介绍个人开源项目 EasyCalculator 的设计思路与实现细节。项目使用 Qt 6.4+QMLC++ 构建,界面简洁,逻辑清晰,适合作为 QML 与 C++/QML 混合开发的入门示例。


一、项目简介

EasyCalculator 是一款桌面计算器应用,支持 Windows、Linux、macOS。界面采用浅灰背景与网格按钮布局,交互风格接近系统自带计算器。

功能一览

类别 功能
四则运算 加、减、乘、除
单目运算 百分比 %、平方 、平方根 √x、倒数 x⁻¹、正负号 +/-
编辑操作 全清 C、清当前项 CE、退格 、小数点 .

界面分为两块显示区:

  • 过程区:显示如 12 + 的运算过程;
  • 结果区:显示当前输入或计算结果。

项目仓库:https://github.com/xcj-fighting/EasyCalculator

演示


二、技术选型

技术 作用
Qt 6.4+ 跨平台 GUI 框架
QML 声明式 UI,快速布局与样式
C++ 计算逻辑、状态管理
CMake 3.20+ 构建与 QML 模块打包

选择 QML 负责界面、C++ 负责逻辑 的分层方式,是 Qt 现代应用开发的常见模式:UI 迭代快,核心算法可单测、可复用。


三、项目结构

EasyCalculator/
├── main.cpp              # 程序入口,加载 QML
├── calculator.h/.cpp     # 计算器核心逻辑
├── Main.qml              # 主窗口与按钮网格
├── buttons/
│   ├── NumberButton.qml       # 数字键样式
│   ├── FunctionalButton.qml   # 功能键样式
│   └── EqualSignButton.qml    # 等号键(强调色)
├── CMakeLists.txt
└── README.md

构建产物通过 qt_add_qml_module 将 QML 与 C++ 注册为同一模块 EasyCalculator,主界面通过 import EasyCalculator 直接使用 Calculator 类型。


四、架构设计:前后端如何协作

整体数据流可以概括为:

用户点击按钮 → QML 调用 Calculator 方法 → C++ 更新状态 → 属性变化 → QML 界面自动刷新

4.1 C++ 侧:暴露给 QML 的 Calculator 类

Calculator 继承 QObject,通过宏与注解注册到 QML 模块:

  • QML_ELEMENT:在 QML 中可直接 Calculator { } 实例化;
  • Q_PROPERTY:绑定 processTextresultText 到界面文本;
  • Q_INVOKABLE:供 QML 调用的槽函数(输入数字、运算符、计算等)。

头文件核心声明如下:

class Calculator : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QString processText READ processText WRITE setProcessText NOTIFY processTextChanged)
    Q_PROPERTY(QString resultText READ resultText WRITE setResultText NOTIFY resultTextChanged)
public:
    Q_INVOKABLE void inputNumber(const QString &number);
    Q_INVOKABLE void inputOperator(const QString &op);
    Q_INVOKABLE void calculate();
    Q_INVOKABLE void clear();
    Q_INVOKABLE void clearEntry();
    Q_INVOKABLE void toggleSign();
    Q_INVOKABLE void backspace();
    Q_INVOKABLE void percent();
    Q_INVOKABLE void square();
    Q_INVOKABLE void squareRoot();
    Q_INVOKABLE void reciprocal();
    // ...
};

4.2 状态机思路

计算器内部用少量成员变量维护状态:

成员 含义
m_resultText 当前显示的数字
m_processText 过程区文案
m_previousValue 上一个操作数
m_operator 当前运算符(+ - × ÷
m_waitingForNewValue 是否等待输入下一个操作数

输入数字时:若处于「等待新值」状态,则覆盖显示;否则在末尾拼接,并防止重复小数点。

输入运算符时:保存当前值为 m_previousValue,过程区显示 前值 + 运算符,结果区重置为 0,并置 m_waitingForNewValue = true

按下等号时:根据 m_operator 做四则运算;除数为 0 时显示「错误」;完成后将结果写回,并允许继续以结果为起点运算。

4.3 四则运算与错误处理示例

void Calculator::calculate()
{
    if (m_operator.isEmpty() || m_previousValue.isEmpty()) return;

    double previousValue = m_previousValue.toDouble();
    double currentValue = m_resultText.toDouble();
    m_processText += QString(" %1 =").arg(m_resultText);

    if (m_operator == "+") {
        m_resultText = QString::number(previousValue + currentValue);
    } else if (m_operator == "-") {
        m_resultText = QString::number(previousValue - currentValue);
    } else if (m_operator == "×") {
        m_resultText = QString::number(previousValue * currentValue);
    } else if (m_operator == "÷") {
        if (currentValue == 0.0) {
            m_resultText = "错误";
        } else {
            m_resultText = QString::number(previousValue / currentValue);
        }
    }

    emit resultTextChanged(m_resultText);
    emit processTextChanged(m_processText);

    m_operator = "";
    m_previousValue = m_resultText;
    m_waitingForNewValue = true;
}

单目函数如平方根会对负数返回「错误」,倒数在除数为 0 时直接忽略,与常见计算器行为一致。


五、QML 界面实现

5.1 主窗口 Main.qml

主界面使用 ColumnLayout + GridLayout(4 列)组织显示区与按钮区。在根节点下声明 C++ 对象:

Calculator {
    id: calculator
}

过程与结果通过属性绑定自动更新:

Text {
    text: calculator.processText
    // ...
}
Text {
    text: calculator.resultText
    font.pixelSize: 44
    // ...
}

按钮点击只需一行绑定,例如:

NumberButton {
    text: "7"
    onClicked: calculator.inputNumber("7")
}
FunctionalButton {
    text: "÷"
    onClicked: calculator.inputOperator("÷")
}
EqualSignButton {
    text: "="
    onClicked: calculator.calculate()
}

窗口固定最小尺寸 328×505,背景色 #f4f2f2,整体偏简洁桌面风格。

5.2 可复用按钮组件

三种按钮组件结构相同,通过 颜色主题 区分用途:

组件 背景风格 用途
NumberButton 白底 数字、+/-.
FunctionalButton 浅灰底 %CEC、运算符等
EqualSignButton 蓝色 #0067c0 等号,视觉强调

每个按钮基于 Rectangle + MouseArea,开启 hoverEnabled,在悬浮与按下时切换背景色与文字色,无需依赖 Button 控件即可获得细腻反馈。

组件内预设 Layout.minimumWidth/HeightfillWidth/fillHeight,便于在 GridLayout 中均匀拉伸。


六、程序入口与 CMake 配置

6.1 main.cpp

使用 QQmlApplicationEngine 加载编译进资源的 QML:

QQmlApplicationEngine engine;
const QUrl url(u"qrc:/EasyCalculator/Main.qml"_qs);
engine.load(url);
return app.exec();

若 QML 加载失败,通过 objectCreated 信号退出进程,避免静默失败。

6.2 CMakeLists.txt 要点

find_package(Qt6 6.4 REQUIRED COMPONENTS Quick)
qt_add_executable(EasyCalculator main.cpp)

qt_add_qml_module(EasyCalculator
    URI EasyCalculator
    VERSION 1.0
    QML_FILES
        Main.qml
        buttons/EqualSignButton.qml
        buttons/FunctionalButton.qml
        buttons/NumberButton.qml
    SOURCES
        calculator.h
        calculator.cpp
)
target_link_libraries(EasyCalculator PRIVATE Qt6::Quick)

qt_add_qml_module 一步完成:QML 文件打包、类型注册、Calculator 与 QML 的链接。这是 Qt 6 推荐的新式 QML 工程写法,比手动维护 .qrc 更清晰。


posted @ 2026-05-20 17:29  fighting_88412  阅读(6)  评论(0)    收藏  举报