在此页面上

计算器

专为横向和纵向设备设计的计算器。它的用户界面使用Qt Quick 自定义组件和响应式布局,应用程序逻辑使用 JavaScript。

计算器示例用户界面

Calqlatr演示了各种 QML 和 Qt Quick功能,如显示自定义组件和使用响应式布局。应用逻辑用 JavaScript 实现,用户界面用 QML 实现。

运行示例

要从 Qt Creator,打开Welcome 模式,并从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行。运行示例后,您就可以将应用程序作为标准计算器使用了。将手机从纵向模式改为横向模式,或在桌面上调整主窗口大小,以激活响应式布局。

使用自定义组件

Calqlatr应用程序使用自定义类型。其中大部分都定义在各自独立的 .qml 文件中:

  • Main.qml
  • content/ApplicationState.qml
  • content/BackspaceButton.qml
  • content/CalculatorButton.qml
  • content/Display.qml
  • content/NumberPad.qml

Main.qml Calqlatr 应用程序包含顶级窗口和根项目。它使用content 目录中的其他自定义类型。例如,Main.qml 中使用了NumberPad 类型(由content/NumberPad.qml 定义)来创建计算器的数字键盘:

        NumberPad {
            id: numberPad
            Layout.margins: root.margin

            isPortraitMode: root.isPortraitMode
            applicationState: state
        }

内联组件允许您在.qml 文件中声明多个组件。示例在NumberPad.qml 中使用内联组件定义了两个组件DigitButtonOperatorButton

    component DigitButton: CalculatorButton {
        onClicked: {
            controller.applicationState.digitPressed(text);
            controller.updateDimmed();
        }
    }

    component OperatorButton: CalculatorButton {
        dimmable: true
        implicitWidth: 48
        textColor: controller.qtGreenColor

        onClicked: {
            controller.applicationState.operatorPressed(text);
            controller.updateDimmed();
        }
    }

这两个组件的类型都是CalculatorButton (如CalculatorButton.qml 中所定义),但为点击信号提供了一个自定义处理程序,并调整了一些属性默认值。DigitButtonOperatorButton 随后在NumberPad.qml 中实例化:

                DigitButton {
                    text: "e"
                    dimmable: true
                    implicitWidth: 48
                }
                OperatorButton { text: "ln" }

有关定义自定义QML组件的更多详情,请查阅《通过 QML 文档定义对象类型》(Defining Object Types throughQMLDocuments)。

响应式布局

在本例中,响应式布局为纵向和横向模式安排了不同的用户界面组件。你可以在Main.qml 中看到这一点,它为纵向模式定义了ColumnLayout ,为横向模式定义了RowLayout

        ColumnLayout {
            id: portraitMode
            anchors.fill: parent
            visible: root.isPortraitMode

            LayoutItemProxy {
                target: display
                Layout.minimumHeight: display.minHeight
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignHCenter
            }
        }

        RowLayout {
            id: landscapeMode
            anchors.fill: parent
            visible: !root.isPortraitMode

            LayoutItemProxy {
                target: display
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignVCenter
            }
        }

ColumnLayout 表示应用程序的纵向布局,而RowLayout 表示横向布局。visible 属性用于处理在特定时间使用哪种布局。NumberPadDisplay 组件的id属性用于设置LayoutItemProxy 类型的target 属性。这样,两种布局就可以使用相同的内容项。此外,还可将LayoutItemProxy 项目内的属性转发到target 本身。例如,当NumberPad 实例化时,两种布局都需要不同的Layout.alignment

NumberPad.qml 中定义NumberPad 本身的纵向和横向布局时,也会使用响应式布局:

        RowLayout {
            spacing: controller.spacing

            GridLayout {
                id: scientificGrid
                columns: 3
                columnSpacing: controller.spacing
                rowSpacing: controller.spacing
                visible: !controller.isPortraitMode

                OperatorButton {
                    text: "x²"
                    Accessible.name: "x squared"
                }
                OperatorButton {
                    text: "⅟x"
                    Accessible.name: "one over x"
                }
                OperatorButton { text: "√" }
                OperatorButton {
                    text: "x³"
                    Accessible.name: "x cubed"
                }
                OperatorButton {
                    text: "sin"
                    Accessible.name: "sine"
                }
                OperatorButton {
                    text: "|x|"
                    Accessible.name: "absolute value"
                }
                OperatorButton { text: "log" }
                OperatorButton {
                    text: "cos"
                    Accessible.name: "cosine"
                }
                DigitButton {
                    text: "e"
                    dimmable: true
                    implicitWidth: 48
                }
                OperatorButton { text: "ln" }
                OperatorButton { text: "tan" }
                DigitButton {
                    text: "π"
                    dimmable: true
                    implicitWidth: 48
                }
            }

            GridLayout {
                id: mainGrid
                columns: 5
                columnSpacing: controller.spacing
                rowSpacing: controller.spacing

                BackspaceButton {
                    onClicked: {
                        controller.applicationState.operatorPressed(this.text);
                        controller.updateDimmed();
                    }
                }

                DigitButton { text: "7" }
                DigitButton { text: "8" }
                DigitButton { text: "9" }
                OperatorButton {
                    text: "÷"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "AC"
                    textColor: controller.backspaceRedColor
                    accentColor: controller.backspaceRedColor
                }
                DigitButton { text: "4" }
                DigitButton { text: "5" }
                DigitButton { text: "6" }
                OperatorButton {
                    text: "×"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "="
                    implicitHeight: 81
                    Layout.rowSpan: 2
                }
                DigitButton { text: "1" }
                DigitButton { text: "2" }
                DigitButton { text: "3" }
                OperatorButton {
                    text: "−"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "±"
                    implicitWidth: 38
                }
                DigitButton { text: "0" }
                DigitButton {
                    text: "."
                    dimmable: true
                }
                OperatorButton {
                    text: "+"
                    implicitWidth: 38
                }
            }
        } // RowLayout

根据controller.isPortraitMode ,数字键盘只包含一个简单计算器的按钮 (mainGrid),或通过scientificGrid 包含一些更高级的功能。

动画按钮文本颜色

CalculatorButton.qml 文件中,数字键盘按钮的文本颜色也是动态的。

        ...
        color: button.getTextColor()
        Behavior on color {
            ColorAnimation {
                duration: 120
                easing.type: Easing.OutElastic
            }
        }
    }

通过在color 属性上定义Behavior ,可以实现颜色的动画变化。当按钮设置为dimmed = true 时,按钮的颜色会变深。按下按钮时,按钮会亮起绿灯。为了动态更改NumberPad 上所有按钮的dimmed 属性,buttonPressed 信号调用NumberPadupdateDimmed() 函数。

    function updateDimmed() {
        for (let i = 0; i < mainGrid.children.length; i++) {
            mainGrid.children[i].dimmed = applicationState.isButtonDisabled(mainGrid.children[i].text);
        }
        for (let j = 0; j < scientificGrid.children.length; j++) {
            scientificGrid.children[j].dimmed = applicationState.isButtonDisabled(
                        scientificGrid.children[j].text);
        }
    }

执行计算

calculator.jsApplicationState.qml 文件定义了计算器的引擎。calculator.js 包含计算器的逻辑状态以及改变状态的操作。ApplicationState.qml 通过 QML 类型公开了这一 API。

让我们先看看calculator.js

let accumulator = 0
let pendingOperator = ""
let lastButton = ""
let digits = ""

accumulator,pendingOperator,lastButton,represent 计算器的逻辑状态。

function isOperationDisabled(op, display) {
    ...
}

isOperationDisabled() 根据当前引擎和显示状态,如果操作op 应被禁用,则返回true 。否则,返回false

function digitPressed(op, display) {
    ...
}

function operatorPressed(op, display) {
    ...
}

digitPressed()operatorPressed() 函数根据按下的按钮更新引擎和显示状态。

ApplicationState.qmlcalculator.js 中定义的 JavaScript 函数公开在一个更漂亮的 QML API 后面:

import QtQml
import "calculator.js" as CalcEngine

calculator.jsCalcEngine 名称导入。

QtObject {
    required property Display display

    function operatorPressed(operator) {
        CalcEngine.operatorPressed(operator, display);
    }
    function digitPressed(digit) {
        CalcEngine.digitPressed(digit, display);
    }
    function isButtonDisabled(op) {
        return CalcEngine.isOperationDisabled(op, display);
    }
}

required property 确保Display.qml 的实例在ApplicationState 中始终可用,其名称为displaydisplay 又用于operatorPressed()digitPressed()isButtonDisabled() 函数。

源文件

Squish GUI 测试

本应用程序带有针对 Qt for Android 的Squish GUI 测试。这些测试使用Squish for Qt制作,用 Python 编写,可在应用程序测试目录下找到。

使用 Squish for Qt for Android 时,请确保使用Qt 内置钩子并用 adb转发 Squish 端口。如果在dlopen 失败时出现 "Cannot load library"(无法加载库)错误,请禁用 CMake 配置中的QT_USE_TARGET_ANDROID_BUILD_DIR ,或在Qt Creator 中清除Projects->Build Settings->CMake->Current Configuration 中的 。

如果没有 Squish 许可证,可以免费试用

示例项目 @ code.qt.io

另请参阅 QML 应用程序

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.