Sur cette page

Calqlatr

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

Exemple d'interface utilisateur d'une calculatrice

Calqlatr illustre diverses fonctionnalités de QML et Qt Quick , telles que l’affichage de composants personnalisés et l’utilisation de mises en page adaptatives. La logique d’application est implémentée en JavaScript et l’interface utilisateur en QML.

Exécution de l'exemple

Vous pouvez exécuter l’exemple à partir de :

Une fois l’exemple exécuté, vous devriez pouvoir utiliser l’application comme une calculatrice standard. Passez 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 adaptative.

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 distinct :

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

Main.qml contient la fenêtre de niveau supérieur 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: appState
        }

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

Ces deux composants sont de type « CalculatorButton » (tel que défini dans CalculatorButton.qml), mais fournissent un gestionnaire personnalisé pour le signal « clicked » et modifient certaines valeurs par défaut des propriétés. « DigitButton » et « OperatorButton » sont ensuite instanciés plus loin dans NumberPad.qml:

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

Consultez la section « Définition de types d’objets via des documents QML » pour plus de détails sur la définition de composants QML personnalisés.

Mises en page adaptatives

Dans cet exemple, des mises en page adaptatives 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 RowLayout pour le mode paysage :

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

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

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

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

Le fichier ColumnLayout représente la mise en page en mode portrait de l’application, tandis que le fichier RowLayout représente la mise en page en mode paysage. Les propriétés visible déterminent quelle mise en page est utilisée à un moment donné. L’attribut id des composants NumberPad et Display sert à définir la propriété target des types LayoutItemProxy. Cela permet aux deux mises en page d’utiliser les mêmes éléments de contenu. De plus, les propriétés peuvent être transmises au sein de l’élément LayoutItemProxy vers l’ target lui-même. Par exemple, lorsque l’ NumberPad est instancié, les deux mises en page nécessitent un Layout.alignment différent.

Une mise en page responsive est également utilisée dans NumberPad.qml lors de la définition des mises en page portrait et paysage pour le 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

Selon controller.isPortraitMode, le pavé numérique contient uniquement les touches d’une calculatrice simple (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 Behavior sur la propriété color. Lorsqu’un bouton est défini sur dimmed = true, il apparaît plus sombre. Lorsqu’on appuie sur un bouton, il s’allume en vert. Afin de modifier dynamiquement la propriété dimmed de tous les boutons du NumberPad, le signal buttonPressed appelle la fonction updateDimmed() de la classe 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. Le fichier calculator.js contient l’état logique de la calculatrice, ainsi que les opérations permettant de modifier cet état. Le fichier ApplicationState.qml expose cette API via un type QML.

Examinons tout d’abord le fichier 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, elle renvoie false.

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

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

digitPressed() Les fonctions ` 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 conviviale :

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 garantit qu’une instance de ` Display.qml ` est toujours disponible dans l’ ApplicationState sous le nom ` display`. ` display ` est à son tour utilisé dans les fonctions ` operatorPressed()`, ` digitPressed() ` et ` isButtonDisabled() `.

Fichiers source

Tests d’interface graphique avec Squish

Cette application est fournie avec des tests d’interface graphique Squish destinés à Qt pour Android. Ces tests sont réalisés avec Squish pour Qt, écrits en Python et se trouvent dans le répertoire de test de l’application.

Lorsque vous utilisez Squish pour Qt sur Android, veillez à utiliser le hook intégré à Qt et à rediriger le port Squish via adb. Si vous obtenez l'erreur « Impossible de charger la bibliothèque » et que la commande « dlopen » échoue, désactivez l'option « QT_USE_TARGET_ANDROID_BUILD_DIR » dans la configuration CMake, ou supprimez-la de la ligne « Projects->Build Settings->CMake->Current Configuration » dans le fichier « Qt Creator ».

Si vous ne disposez pas d’une licence Squish, vous pouvez bénéficier d’un essai gratuit.

Exemple de projet sur code.qt.io

Voir également 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.