En esta página

Calqlatr

Una calculadora diseñada para dispositivos apaisados y verticales. Utiliza componentes personalizados de Qt Quick y diseños adaptables para la interfaz de usuario, y JavaScript para la lógica de la aplicación.

La interfaz de usuario de ejemplo de la calculadora

Calqlatr muestra varias funciones de QML y Qt Quick como la visualización de componentes personalizados y el uso de diseños adaptables. La lógica de la aplicación se implementa en JavaScript y la interfaz de usuario en QML.

Ejecución del ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar. Después de ejecutar el ejemplo, deberías poder utilizar la aplicación como una calculadora estándar. Cambia el teléfono de modo vertical a horizontal -o, en el escritorio, cambia el tamaño de la ventana principal- para activar el diseño responsivo.

Uso de componentes personalizados

La aplicación Calqlatr utiliza tipos personalizados. La mayoría de ellos se definen en su propio archivo .qml:

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

Main.qml contiene la ventana de nivel superior y el elemento raíz. Hace uso de otros tipos personalizados en el directorio content. Por ejemplo, el tipo NumberPad (definido por content/NumberPad.qml) se utiliza en Main.qml para crear el teclado numérico de la calculadora:

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

            isPortraitMode: root.isPortraitMode
            applicationState: state
        }

Los componentesen línea le permiten declarar múltiples componentes dentro de un archivo .qml. El ejemplo utiliza componentes en línea en NumberPad.qml para definir dos componentes, DigitButton y OperatorButton:

    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();
        }
    }

Ambos componentes son del tipo CalculatorButton (tal y como se definen en CalculatorButton.qml), pero proporcionan un controlador personalizado para la señal de clic y modifican algunas propiedades predeterminadas. DigitButton y OperatorButton se instancian posteriormente en NumberPad.qml:

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

Para más información sobre la definición de componentes QML personalizados, consulte Definición de tipos de objeto mediante documentos QML.

Diseños adaptables

En este ejemplo, los diseños adaptables organizan los diferentes componentes de la interfaz de usuario para los modos vertical y horizontal. Esto se puede observar en Main.qml, que define un ColumnLayout para el modo vertical y un RowLayout para el horizontal:

        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 representa el diseño vertical de la aplicación y RowLayout representa el diseño horizontal. Las propiedades visible gestionan qué diseño se utiliza en cada momento. El atributo id de los componentes NumberPad y Display se utiliza para establecer la propiedad target de los tipos LayoutItemProxy. Esto permite que ambas maquetaciones utilicen los mismos elementos de contenido. Además, las propiedades pueden reenviarse dentro del elemento LayoutItemProxy al propio target. Por ejemplo, cuando se instancie NumberPad, ambos diseños necesitarán un Layout.alignment diferente.

En NumberPad.qml también se utiliza un diseño adaptable cuando se define el diseño vertical y horizontal para el propio 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

Dependiendo de controller.isPortraitMode, el teclado numérico contiene sólo los botones de una calculadora simple (mainGrid), o también algunas funcionalidades más avanzadas a través de scientificGrid.

Animación de los colores del texto de los botones

En el archivo CalculatorButton.qml, también se animan los colores del texto de los botones del teclado numérico.

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

Los cambios de color se animan definiendo un Behavior en la propiedad color. Cuando un botón está definido en dimmed = true, el botón aparece más oscuro. Cuando se pulsa un botón, se ilumina en verde. Para cambiar dinámicamente la propiedad dimmed de todos los botones de NumberPad, la señal buttonPressed llama a la función NumberPad's updateDimmed().

    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);
        }
    }

Realizar cálculos

Los archivos calculator.js y ApplicationState.qml definen el motor de la calculadora. calculator.js contiene el estado lógico de la calculadora, así como las operaciones para cambiar el estado. ApplicationState.qml expone esta API a través de un tipo QML.

Veamos primero calculator.js:

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

accumulator, pendingOperator, lastButton, represent el estado lógico de la calculadora.

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

isOperationDisabled() devuelve true si una operación op debe ser desactivada, basándose en el estado actual del motor y de la pantalla. En caso contrario, devuelve false.

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

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

digitPressed() y operatorPressed() actualizan el estado del motor y de la pantalla en función de los botones pulsados.

ApplicationState.qml está exponiendo las funciones JavaScript definidas en calculator.js detrás de una API QML más agradable:

import QtQml
import "calculator.js" as CalcEngine

calculator.js se importa con el nombre CalcEngine.

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 se asegura de que una instancia de Display.qml esté siempre disponible en ApplicationState bajo el nombre display. display se utiliza a su vez en las funciones operatorPressed(), digitPressed() y isButtonDisabled().

Archivos fuente

Pruebas de Squish GUI

Esta aplicación viene con tests GUI de Squish orientados a Qt para Android. Las pruebas se realizan con Squish para Qt y están escritas en Python y se pueden encontrar en el directorio de pruebas de la aplicación.

Cuando utilice Squish para Qt para Android, asegúrese de utilizar el hook incorporado de Qt y reenviar el puerto de Squish con adb. Si recibes el error "Cannot load library" con dlopen fallando, desactiva QT_USE_TARGET_ANDROID_BUILD_DIR de la configuración CMake, o bórralo de Projects->Build Settings->CMake->Current Configuration en Qt Creator.

Si no tienes una licencia de Squish, puedes obtener una prueba gratuita.

Proyecto de ejemplo @ code.qt.io

Ver también Aplicaciones 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.