Sur cette page

Calqlatr

Une calculatrice conçue pour les appareils en mode paysage et portrait. Elle utilise Qt Quick des composants personnalisés et des mises en page réactives pour l'interface utilisateur, et JavaScript pour la logique de l'application.

L'interface utilisateur de l'exemple de calculatrice

Calqlatr fait la démonstration de diverses fonctionnalités QML et Qt Quick telles que l'affichage de composants personnalisés et l'utilisation de mises en page réactives. La logique d'application est mise en œuvre en JavaScript et l'interface utilisateur en QML.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt CreatorOuvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter. Après avoir exécuté l'exemple, vous devriez pouvoir utiliser l'application comme une calculatrice standard. Faites passer le téléphone du mode portrait au mode paysage - ou, sur le bureau, redimensionnez la fenêtre principale - pour activer la mise en page réactive.

Utilisation de composants personnalisés

L'application Calqlatr utilise des types personnalisés. La plupart d'entre eux sont définis dans leur propre fichier .qml :

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

Main.qml contient la fenêtre de premier niveau et l'élément racine. Elle utilise les autres types personnalisés du répertoire content. Par exemple, le type NumberPad (défini par content/NumberPad.qml) est utilisé dans Main.qml pour créer le pavé numérique de la calculatrice :

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

            isPortraitMode: root.isPortraitMode
            applicationState: state
        }

Lescomposants en ligne vous permettent de déclarer plusieurs composants à l'intérieur d'un fichier .qml. L'exemple utilise des composants en ligne dans NumberPad.qml pour définir deux composants, DigitButton et 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();
        }
    }

Les deux composants sont de type CalculatorButton (comme défini dans CalculatorButton.qml), mais fournissent un gestionnaire personnalisé pour le signal "clicked" et modifient certaines propriétés par défaut. DigitButton et OperatorButton sont ensuite instanciés dans NumberPad.qml:

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

Pour plus de détails sur la définition de composants QML personnalisés, consultez la section Définir des types d'objets à l'aide de documents QML.

Mises en page réactives

Dans cet exemple, les mises en page réactives organisent les différents composants de l'interface utilisateur pour les modes portrait et paysage. Vous pouvez le constater dans Main.qml, qui définit un ColumnLayout pour le mode portrait et un RowLayout pour le mode paysage :

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

Le ColumnLayout représente la disposition en mode portrait pour l'application, et le RowLayout représente la disposition en mode paysage. Les propriétés visible gèrent la disposition utilisée à un moment donné. L'attribut id des composants NumberPad et Display est utilisé pour définir la propriété target des types LayoutItemProxy. Cela permet aux deux présentations d'utiliser les mêmes éléments de contenu. En outre, les propriétés peuvent être transférées de l'élément LayoutItemProxy à l'élément target lui-même. Par exemple, lorsque l'élément NumberPad est instancié, les deux présentations requièrent un élément Layout.alignment différent.

Une mise en page réactive est également utilisée dans NumberPad.qml lors de la définition de la mise en page portrait et paysage pour le site NumberPad lui-même :

        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

En fonction de controller.isPortraitMode, le pavé numérique ne contient que les boutons d'une simple calculatrice (mainGrid), ou également des fonctionnalités plus avancées via scientificGrid.

Animation des couleurs du texte des boutons

Dans le fichier CalculatorButton.qml, les couleurs du texte des boutons du pavé numérique sont également animées.

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

Les changements de couleur sont animés en définissant une propriété Behavior sur color. Lorsqu'un bouton est défini sur dimmed = true, il apparaît plus sombre. Lorsqu'un bouton est pressé, il s'allume en vert. Pour modifier dynamiquement la propriété dimmed de tous les boutons du site NumberPad, le signal buttonPressed appelle la fonction updateDimmed() du site NumberPad.

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

Effectuer des calculs

Les fichiers calculator.js et ApplicationState.qml définissent le moteur de la calculatrice. calculator.js contient l'état logique de la calculatrice, ainsi que les opérations permettant de modifier cet état. ApplicationState.qml expose cette API par le biais d'un type QML.

Jetons d'abord un coup d'œil à calculator.js:

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

accumulator, pendingOperator, lastButton, represent l'état logique de la calculatrice.

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

isOperationDisabled() renvoie true si une opération op doit être désactivée, en fonction de l'état actuel du moteur et de l'affichage. Sinon, il renvoie false.

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

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

digitPressed() et operatorPressed() mettent à jour l'état du moteur et de l'affichage en fonction des boutons enfoncés.

ApplicationState.qml expose les fonctions JavaScript définies dans calculator.js derrière une API QML plus agréable :

import QtQml
import "calculator.js" as CalcEngine

calculator.js est importé sous le nom 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 s'assure qu'une instance de Display.qml est toujours disponible dans ApplicationState sous le nom display. display est à son tour utilisé dans les fonctions operatorPressed(), digitPressed() et isButtonDisabled().

Fichiers sources

Test de l'interface graphique Squish

Cette application est livrée avec des tests Squish GUI destinés à Qt Test pour Android. Les tests sont réalisés avec Squish for Qt et sont écrits en Python. Ils se trouvent dans le répertoire de test de l'application.

Lorsque vous utilisez Squish pour Qt pour Android, assurez-vous d'utiliser le hook intégré à Qt et de transférer le port Squish avec adb. Si vous obtenez l'erreur "Cannot load library" lorsque dlopen échoue, désactivez QT_USE_TARGET_ANDROID_BUILD_DIR dans la configuration CMake, ou effacez-la de Projects->Build Settings->CMake->Current Configuration dans Qt Creator.

Si vous n'avez pas de licence Squish, vous pouvez obtenir une version d'essai gratuite.

Exemple de projet @ code.qt.io

Voir aussi Applications 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.