このページでは

Calqlatr

横向きと縦向きのデバイス用にデザインされた電卓。ユーザーインターフェイスにはQt Quick カスタムコンポーネントとレスポンシブレイアウトを、アプリケーションロジックには JavaScript を使用しています。

電卓のサンプルUI

Calqlatrは様々なQMLと Qt Quickカスタムコンポーネントの表示やレスポンシブレイアウトの使用など、さまざまなQMLとその特徴を示しています。アプリケーションロジックはJavaScript、UIはQMLで実装されています。

サンプルを実行する

からサンプルを実行するには Qt Creatorからサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Qt Creator:Tutorialを参照してください:ビルドと実行サンプルを実行すると、標準的な電卓としてアプリケーションを使用できるようになります。レスポンシブレイアウトを有効にするには、携帯電話を縦向きから横向きに変えるか、デスクトップではメインウィンドウのサイズを変更してください。

カスタムコンポーネントの使用

Calqlatrアプリケーションはカスタムタイプを使用します。そのほとんどは別の.qmlファイルで定義されています:

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

Main.qml にはトップレベルウィンドウとルートアイテムが含まれています。content ディレクトリにある他のカスタムタイプを利用します。例えば、NumberPad 型(content/NumberPad.qml で定義)は、Main.qml で電卓の数字パッドの作成に使用されます:

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

            isPortraitMode: root.isPortraitMode
            applicationState: state
        }

インラインコンポーネントでは.qml ファイル内で複数のコンポーネントを宣言できます。この例では、NumberPad.qml でインライン・コンポーネントを使用し、DigitButtonOperatorButton の2つのコンポーネントを定義しています:

    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ドキュメントによるオブジェクトタイプの定義」を参照してください。

レスポンシブレイアウト

この例では、レスポンシブ・レイアウトによって、縦向きと横向きの両方に異なるUIコンポーネントを配置しています。このことは、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 自体のポートレート・レイアウトとランドスケープ・レイアウトを定義する際にも、NumberPad.qml で使用されます:

        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.qml は、calculator.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 display display operatorPressed() digitPressed() isButtonDisabled()

ソースファイル

Squish GUIのテスト

このアプリケーションには、Qt for Androidを対象としたSquish GUIテストが付属しています。テストはSquish for Qtで作成され、Pythonで書かれており、アプリケーションのtestディレクトリにあります。

Squish for Qt for Androidを使用する場合は、必ずQt組み込みのフックを使用し、adbでSquishポートを転送してください。dlopen が失敗して "Cannot load library" というエラーが表示される場合は、CMake 設定からQT_USE_TARGET_ANDROID_BUILD_DIR を無効にするか、Qt CreatorProjects->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.