Calqlatr

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

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

サンプルを実行する

からサンプルを実行するには Qt Creatorからサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Exampleを参照してください。

カスタムコンポーネントの表示

Calqlatrアプリケーションでは、カスタム・タイプが使用される。これらはそれぞれ別の.qmlファイルで定義されています:

  • BackspaceButton.qml。
  • 電卓ボタン.qml
  • 表示.qml
  • ナンバーパッド.qml

これらのカスタム・タイプをMain.qml で使用するには、content フォルダにインポート文を追加します:

import "content"

例えば、NumberPad 型は電卓の数字パッドを作成するためにMain.qml で使用されます。これはQt Quick のすべてのビジュアルアイテムの基本タイプであるItem タイプの中にネストされています:

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

カスタムコンポーネントはどのQMLファイルでも定義可能なQMLタイプで、NumberPad.qml のように独自の.qmlファイルで定義されたコンポーネントと同じ動作をします。NumberPad.qml ではDigitButton コンポーネントとOperatorButton コンポーネントが定義されています。これらのコンポーネントでは、新しいプロパティを追加したり、既存のプロパティを変更したりすることができます。ここで、onReleased ハンドラーは、カスタム・コンポーネントの両方に対して上書きされます。

    component DigitButton: CalculatorButton {
        onReleased: {
            root.digitPressed(text)
            updateDimmed()
        }
    }

    component OperatorButton: CalculatorButton {
        onReleased: {
            root.operatorPressed(text)
            updateDimmed()
        }
        textColor: controller.qtGreenColor
        implicitWidth: 48
        dimmable: true
    }

さらに、NumberPad のさまざまなボタンには、CalculatorButton タイプを使用します。CalculatorButton.qml はボタンの基本プロパティを定義しており、NumberPad.qml で各インスタンスごとに変更します。数字ボタンと演算子ボタンには、textwidthdimmable プロパティなど、いくつかの追加プロパティがあります。電卓エンジンがそのボタンからの入力を受け付けないときはいつでも、dimmable を使用してボタンを視覚的に無効にします。

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

content ディレクトリにはBackSpaceButton.qml という別のファイルがあります。これはCalculatorButton の特殊なケースで、テキストではなく画像をボタンにレンダリングします。このボタンはOperatorButton と同じですが、text ではなくicon を含んでいます:

    icon.source: getIcon()
    icon.width: 38
    icon.height: 38
    icon.color: getIconColor()
    // include this text property as the calculator engine
    // differentiates buttons through text. The text is never drawn.
    text: "bs"

    property bool dimmable: true
    property bool dimmed: false
    readonly property color backgroundColor: "#222222"
    readonly property color borderColor: "#A9A9A9"
    readonly property color backspaceRedColor: "#DE2C2C"
    readonly property int buttonRadius: 8

    function getBackgroundColor() {
        if (button.dimmable && button.dimmed)
            return backgroundColor
        if (button.pressed)
            return backspaceRedColor
        return backgroundColor

レスポンシブレイアウト

この例では、レスポンシブ・レイアウトは、さまざまなUIコンポーネントを縦向きと横向きの両方に配置します。また、この2つのモードを切り替えることもできます。このことは、Main.qml 、ポートレート・モード用のColumnLayout 、ランドスケープ用のRowLayout を定義していることでわかります。

        ColumnLayout {
            id: portraitMode
            anchors.fill: parent
            visible: true

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

        RowLayout {
            id: landscapeMode
            anchors.fill: parent
            visible: false

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

ColumnLayout はアプリケーションのポートレート・レイアウトを表し、RowLayout はランドスケープ・レイアウトを表します。visible プロパティは、ある時点でどちらのレイアウトを使用するかを処理します。NumberPadDisplay コンポーネントのid プロパティは、LayoutItemProxy タイプのtarget プロパティを設定するために使用されます。これにより、両方のレイアウトで同じコンテンツ・アイテムを使用することができます。さらに、LayoutItemProxy アイテム内のプロパティをtarget 自身に転送することもできます。例えば、NumberPad がインスタンス化されると、両方のレイアウトは異なるLayout.alignment を必要とします。

2つのレイアウトを切り替えるには、isPortraitMode プロパティのシグナルハンドラで、それぞれの可視性を設定します:

        onIsPortraitModeChanged: {
            if (isPortraitMode) {
                portraitMode.visible = true
                landscapeMode.visible = false
            } else {
                portraitMode.visible = false
                landscapeMode.visible = true
            }
        }

これは、QMLがすべての自己宣言プロパティに対してシグナルハンドラを作成するためです。この場合、on<Property>Changed ハンドラで、<property> はisPortraitMode プロパティです。

レスポンシブ・レイアウトは、NumberPad.qmlNumberPad 自体の縦長と横長のレイアウトを定義する際にも使われます。

        RowLayout {
            spacing: controller.spacing

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

                OperatorButton { text: "x²" }
                OperatorButton { text: "⅟x" }
                OperatorButton { text: "√" }
                OperatorButton { text: "x³" }
                OperatorButton { text: "sin" }
                OperatorButton { text: "|x|" }
                OperatorButton { text: "log" }
                OperatorButton { text: "cos" }
                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 {}
                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

この場合、2つのLayoutItemProxy アイテムが作成されます。それらのtarget プロパティは、すべての科学的ボタンを含むGrid タイプのscientificGrid と、すべての標準ボタンを含むGrid タイプのmainGrid に設定されます。

CalculatorButton.qml では、数字パッド・ボタンの文字色もアニメーション化されている。

        ...
        color: getBackgroundColor()
        border.color: getBorderColor()
    }

    contentItem: Text {
        text: button.text
        font.pixelSize: button.fontSize
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        color: 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 = root.isButtonDisabled(mainGrid.children[i].text)
        }
        for (let j = 0; j < scientificGrid.children.length; j++){
            scientificGrid.children[j].dimmed = root.isButtonDisabled(scientificGrid.children[j].text)
        }
    }

計算の実行

calculator.jsファイルは電卓のエンジンを定義します。このファイルには、電卓の状態を保存する変数と、ユーザーが数字や演算子のボタンを押したときに呼び出される関数が含まれています。このエンジンを使用するには、CalcEngine というエイリアスを使用して、Main.qml ファイルに calculator.js をインポートします:

import "content/calculator.js" as CalcEngine

デフォルトでは、QMLからJavaScriptファイルをインポートすると、そのファイルの新しいインスタンスが作成され、そのファイルに含まれる状態はそのインスタンスに固有のものとなります。.pragma library を使うことで、スクリプトを使うすべてのユーザ間で状態を共有することができます。

.pragma library

ユーザーが数字を押すと、その数字のテキストがディスプレイに表示されます。演算子を押すと、適切な計算が実行され、等号(=)演算子を使用して結果を表示できる。オールクリア(AC)演算子は、電卓エンジンをリセットする。

ファイル一覧

サンプルプロジェクト @ code.qt.io

QMLアプリケーションも参照してください

© 2025 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.