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

Calqlatrは、カスタムコンポーネントの表示やレスポンシブレイアウトの活用など、QMLのさまざまな Qt Quick 機能を実演しています。アプリケーションロジックはJavaScriptで、UIはQMLで実装されています。
例の動作確認
以下の手順でサンプルを実行できます:
- Qt Creator
Welcome モードを開き、「Examples 」からこのサンプルを選択してください。詳細については、Qt Creator の「チュートリアル:ビルドと実行」を参照してください。
- Qt Extension for Visual Studio Code
Command Palette から `Qt: Open Qt examples ` コマンドを実行し、リストからサンプルを選択します。詳細については、Qt Extension for Visual Studio Code の「チュートリアル: ビルドと実行」を参照してください。
サンプルを実行すると、そのアプリケーションを標準的な電卓として使用できるようになります。スマートフォンを縦向きから横向きに切り替えるか、デスクトップ環境ではメインウィンドウのサイズを変更して、レスポンシブレイアウトを有効にしてください。
カスタムコンポーネントの使用
Calqlatrアプリケーションではカスタム型が使用されています。そのほとんどは、個別の .qml ファイルで定義されています:
Main.qmlcontent/ApplicationState.qmlcontent/BackspaceButton.qmlcontent/CalculatorButton.qmlcontent/Display.qmlcontent/NumberPad.qml
Main.qml には、最上位のウィンドウとルートアイテムが含まれています。このウィンドウは、content ディレクトリ内の他のカスタム型を利用しています。たとえば、NumberPad 型(content/NumberPad.qml で定義)は、Main.qml 内で電卓の数字パッドを作成するために使用されています:
NumberPad {
id: numberPad
Layout.margins: root.margin
isPortraitMode: root.isPortraitMode
applicationState: appState
}インラインコンポーネントを使用すると、.qml ファイル内に複数のコンポーネントを宣言できます。この例では、NumberPad.qml 内のインラインコンポーネントを使用して、DigitButton とOperatorButton の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 で定義されている通り)ですが、clicked シグナルに対するカスタムハンドラを提供し、一部のプロパティのデフォルト値を調整しています。その後、DigitButton とOperatorButton は、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: 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
}
}ColumnLayout はアプリケーションの縦向きレイアウトを表し、RowLayout は横向きレイアウトを表します。visible のプロパティは、その時点でどのレイアウトを使用するかを制御します。NumberPad およびDisplay コンポーネントのid属性は、LayoutItemProxy 型のtarget プロパティを設定するために使用されます。これにより、両方のレイアウトで同じコンテンツアイテムを使用できるようになります。さらに、LayoutItemProxy アイテム内でプロパティをtarget 自体に転送することも可能です。たとえば、NumberPad がインスタンス化される際、両方のレイアウトで異なるLayout.alignment が必要となります。
レスポンシブレイアウトは、NumberPad.qml において、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
}
}
} // RowLayoutcontroller.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 シグナルはNumberPad の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);
}
}計算の実行
calculator.js およびApplicationState.qml ファイルは、電卓のエンジンを定義しています。calculator.js には、電卓の論理状態と、その状態を変更するための操作が定義されています。ApplicationState.qml は、このAPIをQML型を通じて公開しています。
まずは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 CalcEnginecalculator.js は、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 これにより、ApplicationState 内で、Display.qml のインスタンスが常に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 Creator 内の `Projects->Build Settings->CMake->Current Configuration ` からこれを削除してください。
Squishのライセンスをお持ちでない場合は、無料トライアルをご利用いただけます。
「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.