QMLで学ぶQt Quick:基礎から始めるUI開発ガイド


QMLで学ぶQt Quick:基礎から始めるUI開発ガイド

はじめに

現代のソフトウェア開発において、ユーザーインターフェース(UI)はアプリケーションの成功を左右する重要な要素です。直感的で応答性が高く、魅力的なUIは、ユーザー体験を向上させ、アプリケーションの利用価値を高めます。しかし、複雑なUIを効率的に開発し、さらに複数のプラットフォーム(デスクトップ、モバイル、組み込みシステムなど)で動作させることは、しばしば困難な課題となります。

そこで登場するのが、QtフレームワークとそのUI開発キットであるQt Quickです。Qtは、C++をベースとした強力なクロスプラットフォームアプリケーション開発フレームワークであり、グラフィカルUI、ネットワーク、データベースアクセス、マルチメディア、並列処理など、幅広い機能を提供します。その中でもQt Quickは、宣言的なアプローチとQML言語を用いて、アニメーション豊かな、流れるような現代的なUIを迅速に構築するために設計されています。

この記事では、QMLとQt Quickに焦点を当て、その基礎から始めてUI開発の基本的な手法を習得することを目指します。QMLの基本的な構文、UI要素の配置、ユーザーインタラクションの処理、動的なUIの構築、コンポーネント化、アニメーション、そしてC++との連携まで、順を追って丁寧に解説していきます。

Qt Quickは、デスクトップアプリケーション(Windows, macOS, Linux)、モバイルアプリケーション(Android, iOS)、組み込みシステムなど、様々な環境で同じコードベースからリッチなUIを開発できる強力なツールです。この記事を通じて、Qt Quickを使ったUI開発の第一歩を踏み出し、あなたのアイデアを魅力的なアプリケーションとして形にするための基礎を築きましょう。

この記事は、プログラミングの基本的な知識(変数、関数、オブジェクト指向の基本的な概念など)をお持ちの方を対象としています。QtやQMLに触れるのが初めての方でも理解できるように、基礎から丁寧に解説を行います。

さあ、QMLとQt Quickの世界へ飛び込み、モダンなUI開発の楽しさを体験しましょう。

第1章:Qt QuickとQMLの基礎

Qt QuickによるUI開発の中心となるのが、QML(Qt Modeling Language)という言語です。QMLは、UIの構造や外観、振る舞いを記述するための宣言的な言語です。従来の命令型プログラミング言語(C++, Java, Pythonなど)のように「どうやって」UIを構築するかをステップバイステップで記述するのではなく、「どのような」UI要素があり、それらが「どのように」配置され、「どのような」関係性を持つかを記述します。

1.1 QMLとは何か?

QMLはJavaScriptをベースにしており、JavaScriptの式や関数を使用することができます。しかし、その主な目的はUI要素(矩形、テキスト、画像、ボタンなど)を階層的に定義し、それらのプロパティ(位置、サイズ、色など)を設定することです。

QMLの最大の特長は、その宣言的な性質と、プロパティバインディングです。

  • 宣言的: UI要素とその関係性を記述するだけで、Qt Quickがそれらをどのようにレンダリングするかを自動的に処理します。これにより、複雑なUI構造も直感的に記述できます。
  • プロパティバインディング: あるオブジェクトのプロパティの値を、別のオブジェクトのプロパティや計算結果に紐付けることができます。紐付けられたプロパティは、元の値が変更されると自動的に更新されます。これにより、UI要素間の依存関係を簡単に表現し、データの変化にUIが自動的に追従するような、応答性の高いUIを構築できます。

1.2 Qt Quickとは何か?

Qt Quickは、QML言語を用いて、アニメーションや視覚効果に富んだ、滑らかなUIを開発するためのフレームワークです。Qt Quickは、グラフィックスハードウェア(GPU)のアクセラレーションを積極的に利用するため、高性能なUIレンダリングが可能です。

Qt Quickモジュールには、基本的な視覚要素(Rectangle, Image, Textなど)や、より複雑な要素(ListView, GridView, Button, Sliderなど)、アニメーション機能、状態管理機能、入出力処理機能などが含まれています。

1.3 なぜQML/Qt Quickを学ぶのか?

QML/Qt Quickを学ぶことには多くのメリットがあります。

  • 迅速なUI開発: 宣言的なQMLは、UIの構造を直感的に記述でき、プロパティバインディングやコンポーネント化の仕組みにより、UI開発のイテレーションサイクルを大幅に短縮できます。
  • モダンでリッチなUI: アニメーションやトランジションの機能が豊富に用意されており、ハードウェアアクセラレーションにより滑らかな描画が可能です。これにより、ユーザー体験の高い、視覚的に魅力的なUIを容易に実現できます。
  • クロスプラットフォーム: Qtフレームワークのクロスプラットフォーム性に加えて、QMLコード自体も基本的にプラットフォーム非依存で記述できます。一度書いたUIコードを、デスクトップ、モバイル、組み込みなど、様々なプラットフォームで再利用できます。
  • C++との連携: UIの見た目や簡単な振る舞いはQMLで記述し、パフォーマンスが要求される処理や複雑なロジック、既存のバックエンド処理はC++で記述し、それらをシームレスに連携させることができます。これは、ネイティブなパフォーマンスと迅速なUI開発を両立できる強力な組み合わせです。
  • コミュニティとエコシステム: Qtは長い歴史を持つ成熟したフレームワークであり、活発なコミュニティと豊富なドキュメント、ツール(Qt Creatorなど)が存在します。

1.4 QMLの基本的な構文

QMLコードは、通常 .qml という拡張子のファイルに記述されます。基本的な構成要素は「オブジェクト」です。

“`qml
// main.qml
import QtQuick 2.15 // 必要なモジュールをインポート

Rectangle { // オブジェクトの型名
id: root // オブジェクトに一意なIDを割り当てる(省略可)

width: 400 // プロパティとその値
height: 300
color: "lightblue" // 色は文字列で指定可能

// 子オブジェクト
Text {
    id: helloText
    text: "Hello, QML!"
    color: "darkblue"
    font.pointSize: 24 // fontはオブジェクトのプロパティ
    anchors.centerIn: parent // anchorsによる位置決め
}

}
“`

  • import: 使用するQMLモジュールを指定します。Qt Quickの基本要素を使うには import QtQuick 2.15 のように記述します。バージョン番号は環境に合わせて変更してください。
  • オブジェクト: Rectangle, Text など、UI要素の型名を記述します。波括弧 {} の中にそのオブジェクトの設定を記述します。
  • id: オブジェクトに一意な識別子を割り当てます。これにより、他のオブジェクトからそのオブジェクトを参照できるようになります。IDはファイル内でユニークである必要があります。
  • プロパティ: オブジェクトは様々なプロパティを持ちます(width, height, color, text, font など)。プロパティ名: 値 の形式で設定します。値は数値、文字列、真偽値、他のオブジェクトへの参照、JavaScript式など様々です。
  • ネスト: オブジェクトは他のオブジェクトの子としてネストできます。上の例では、Text オブジェクトは Rectangle オブジェクトの子になっています。親オブジェクトの領域内に子オブジェクトが配置されます。

1.5 プロパティバインディング

プロパティバインディングは、QMLの中心的な概念です。あるプロパティの値を、別のプロパティの値や式の結果に動的に紐付けます。

“`qml
Rectangle {
id: rect1
width: 100
height: 100
color: “red”

Rectangle {
    id: rect2
    width: parent.width / 2 // 親の幅の半分にバインド
    height: parent.height / 2 // 親の高さの半分にバインド
    color: "blue"
    x: parent.width / 4 // 親の幅の1/4の位置にバインド
    y: parent.height / 4 // 親の高さの1/4の位置にバインド
}

}
``
この例では、
rect2width,height,x,yプロパティが、親であるrect1widthheightプロパティにバインドされています。もしrect1widthheightが実行中に変更されると、rect2` の対応するプロパティも自動的に再計算されて更新されます。

バインディングは、プロパティ設定の右辺にJavaScript式を記述することで行われます。他のオブジェクトを参照するには、そのオブジェクトの id を使用します。

1.6 シグナルとスロット

QMLオブジェクトは、特定のイベントが発生したときにシグナルを発することができます(例: ボタンがクリックされた、プロパティの値が変更された)。他のオブジェクトは、このシグナルを受け取って、それに応じた処理(スロット)を実行できます。これはQtの主要なメカニズムの一つです。

QMLでは、特定のシグナルに対応するハンドラプロパティが用意されていることが多いです。例えば、MouseArea オブジェクトには onClicked というシグナルハンドラがあります。

“`qml
Rectangle {
width: 200
height: 200
color: “red”

MouseArea {
    anchors.fill: parent // 親(Rectangle)全体をマウスイベント領域とする
    onClicked: { // クリックシグナルが発生したときのスロット(JavaScriptコードブロック)
        parent.color = "blue" // 親(Rectangle)の色を青に変更
        console.log("Rectangle clicked!") // デバッグ出力
    }
}

}
``
この例では、
Rectangleの上に配置されたMouseAreaがクリックされると、onClickedハンドラ内のJavaScriptコードが実行され、親であるRectangle` の色が赤から青に変わります。

また、任意のプロパティの値が変更されたときに発生するシグナル (on<PropertyName>Changed) を受け取ることもできます。

“`qml
Rectangle {
id: myRect
width: 100
height: 100
color: “red”

// colorプロパティが変更されたときに呼ばれるハンドラ
onColorChanged: {
    console.log("Color changed to: " + myRect.color)
}

MouseArea {
    anchors.fill: parent
    onClicked: {
        myRect.color = myRect.color === "red" ? "blue" : "red" // クリックで色を切り替え
    }
}

}
``onColorChangedハンドラは、myRect.color` の値が変更されるたびに実行されます。

1.7 簡単なサンプルコード (Hello, World!)

最小限のQMLアプリケーションは以下のようになります。

“`qml
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15 // ウィンドウを表示するために必要

Window {
width: 400
height: 300
visible: true // ウィンドウを表示
title: “Hello QML”

Rectangle {
    width: 200
    height: 100
    color: "green"
    anchors.centerIn: parent // ウィンドウの中央に配置

    Text {
        text: "Hello, QML World!"
        anchors.centerIn: parent // Rectangleの中央に配置
        color: "white"
        font.pointSize: 18
    }
}

}
“`
このコードは、タイトルが “Hello QML” のウィンドウを作成し、その中央に緑色の矩形を配置し、さらにその矩形の中央に “Hello, QML World!” という白いテキストを表示します。

このQMLファイルを実行するには、Qt Creatorを使用するか、C++のQtアプリケーションからQMLエンジンを用いて読み込む必要があります。Qt Creatorは、QMLの編集、プレビュー、デバッグ機能を統合した強力な開発環境であり、Qt Quick開発の標準ツールです。環境構築やプロジェクト作成の詳細はQtの公式ドキュメントやQt Creatorのヘルプを参照してください。Qt Creatorで「Qt Quick Application」プロジェクトを作成し、生成された main.qml を上記のコードに置き換えて実行すれば、このウィンドウが表示されます。

第2章:基本的な要素 (Basic Elements)

Qt Quickでは、UIを構成するための様々な基本的な要素(アイテム)が提供されています。これらの要素は通常、Item という最も基本的な要素から派生しています。

2.1 Item: QMLの基本要素

Item は、視覚的な表示を持ちませんが、他のアイテムを包含したり、座標系を提供したりするための基本的なコンテナです。すべての視覚的なアイテム(Rectangle, Image, Text など)は Item を継承しています。

Item が持つ主要なプロパティは以下の通りです。

  • id: アイテムの識別子。
  • x, y: アイテムの親からの相対位置(左上隅を基準)。
  • width, height: アイテムの幅と高さ。
  • visible: アイテムが表示されているか (true/false)。
  • opacity: アイテムの不透明度(0.0で完全に透明、1.0で完全に不透明)。
  • rotation: アイテムの回転角度(度)。
  • scale: アイテムの拡大縮小率。
  • children: アイテムの子要素のリスト(自動的に管理される)。
  • parent: アイテムの親要素(自動的に管理される)。
  • anchors.*: アンカーによる位置決めプロパティ。
  • state: アイテムの現在の状態。
  • transform: 変形(平行移動、回転、拡大縮小など)を適用するためのリスト。

Item 自体は何も描画しませんが、子要素の配置や可視性の管理、座標変換などに使用されます。例えば、複数のアイテムをまとめて移動・回転させたい場合に、それらを Item の子として配置し、親の Item を操作する、といった使い方ができます。

2.2 視覚要素 (VisualItem の子孫)

VisualItemItem から派生したクラスで、描画機能を持つアイテムの基底クラスです。Qt Quickでよく使われる視覚要素のほとんどは VisualItem の子孫です。

  • Rectangle:
    最も基本的な図形要素で、矩形を描画します。色、サイズ、位置、角丸などを設定できます。

    qml
    Rectangle {
    width: 100
    height: 100
    color: "orange"
    x: 50
    y: 50
    radius: 10 // 角丸
    border.color: "darkred" // 枠線の色
    border.width: 2 // 枠線の太さ
    opacity: 0.8 // 少し透明に
    }

  • Image:
    画像ファイル(PNG, JPEG, GIFなど)を表示します。

    qml
    Image {
    id: myImage
    source: "path/to/your/image.png" // 画像ファイルのパス (ローカルファイルまたはURL)
    width: 150
    height: 150
    fillMode: Image.PreserveAspectFit // アスペクト比を維持してコンテナに収める
    // fillMode: Image.Stretch // コンテナに合わせて伸縮
    // fillMode: Image.Tile // タイル表示
    // fillMode: Image.PreserveAspectCrop // アスペクト比を維持してコンテナを埋める(はみ出し部分は切り取り)
    }

    source プロパティには、ローカルファイルのパス(アプリケーションの実行ファイルからの相対パスやリソースファイルパス)またはウェブ上のURLを指定できます。画像は非同期でロードされます。

  • Text:
    テキストを表示します。フォント、色、サイズ、配置、折り返しなどを設定できます。リッチテキスト(HTMLタグ)にも対応しています。

    qml
    Text {
    width: 200
    text: "これは表示するテキストです。"
    color: "#333333"
    font.family: "Arial"
    font.pointSize: 16
    font.bold: true
    wrapMode: Text.WordWrap // 単語単位で折り返す
    horizontalAlignment: Text.AlignHCenter // 水平方向中央寄せ
    verticalAlignment: Text.AlignVCenter // 垂直方向中央寄せ
    // textFormat: Text.RichText // リッチテキストを有効にする場合
    // text: "<b>太字</b>と<i>イタリック</i>" // リッチテキスト例
    }

    font プロパティはオブジェクトであり、family, pointSize, bold, italic などのプロパティを持ちます。

2.3 位置決め (Positioning)

QMLでアイテムを配置する方法はいくつかあります。

  • 絶対位置:
    x, y プロパティを直接設定する方法です。親アイテムの左上隅 (0,0) を基準とした座標で位置を指定します。最も単純ですが、ウィンドウサイズや他の要素のサイズが変更された場合に自動調整されないため、柔軟性に欠けます。

    qml
    Rectangle {
    width: 100; height: 100; color: "red"; x: 10; y: 10
    }
    Rectangle {
    width: 100; height: 100; color: "blue"; x: 120; y: 10
    }

  • アンカー (Anchors):
    これがQMLで最も推奨される位置決め方法です。アイテムの辺(left, right, top, bottom)、中心(horizontalCenter, verticalCenter, centerIn)、ベースラインを、親アイテムや他の兄弟アイテムの対応する辺や中心に紐付けます。これにより、相対的な位置関係を宣言的に定義できます。

    “`qml
    Rectangle { // 親
    width: 300; height: 200; color: “lightgray”

    Rectangle { // 子
        width: 50; height: 50; color: "red"
        anchors.centerIn: parent // 親の中央に配置
    }
    
    Rectangle { // 別の兄弟
        width: 50; height: 50; color: "blue"
        anchors.left: parent.left // 親の左端に合わせる
        anchors.top: parent.top // 親の上端に合わせる
        anchors.leftMargin: 10 // 左端から10ピクセルのマージン
        anchors.topMargin: 10 // 上端から10ピクセルのマージン
    }
    
    Rectangle { // 別の兄弟
        width: 50; height: 50; color: "green"
        anchors.right: parent.right // 親の右端に合わせる
        anchors.bottom: parent.bottom // 親の下端に合わせる
        anchors.rightMargin: 10
        anchors.bottomMargin: 10
    }
    
    Rectangle { // さらに別の兄弟
        width: 50; height: 50; color: "yellow"
        anchors.horizontalCenter: parent.horizontalCenter // 親の水平方向中央に合わせる
        anchors.bottom: parent.bottom // 親の下端に合わせる
        anchors.bottomMargin: 10
    }
    

    }
    ``
    *
    anchors.fill: target: ターゲットアイテムと同じ位置とサイズにする。
    *
    anchors.centerIn: target: ターゲットアイテムの中央に配置する。
    *
    anchors.left: target.right: 自身の左端をターゲットの右端に合わせる。
    *
    anchors.right: target.left: 自身の右端をターゲットの左端に合わせる。
    *
    anchors.top: target.bottom: 自身の上端をターゲットの下端に合わせる。
    *
    anchors.bottom: target.top: 自身の下端をターゲットの上端に合わせる。
    *
    anchors.horizontalCenter: target.horizontalCenter: 自身の水平方向中央をターゲットの水平方向中央に合わせる。
    *
    anchors.verticalCenter: target.verticalCenter: 自身の垂直方向中央をターゲットの垂直方向中央に合わせる。
    * マージン (
    anchors.leftMargin,anchors.rightMarginなど) やオフセット (anchors.horizontalCenterOffset,anchors.verticalCenterOffset` など) を使って、位置を微調整できます。

    アンカーは非常に強力で柔軟な位置決め方法であり、ウィンドウサイズや要素サイズの変化に自動的に対応するレスポンシブなUIを構築するのに適しています。

  • レイアウト (Layouts):
    Qt Quickには、一般的なレイアウト(行、列、グリッドなど)を簡単に実現するためのレイアウトマネージャーがあります。これらは Qt Quick Layouts モジュールに含まれており、通常 import QtQuick.Layouts 1.15 のようにインポートして使用します。主なレイアウトアイテムには RowLayout, ColumnLayout, GridLayout, StackLayout などがあります。これらのレイアウトアイテムの子要素として配置されたアイテムは、レイアウトによって自動的にサイズや位置が管理されます。

    “`qml
    import QtQuick.Layouts 1.15

    ColumnLayout { // 垂直方向に要素を並べる
    width: 200
    height: 150
    spacing: 10 // 要素間のスペース

    Rectangle {
        width: 80; height: 30; color: "red"
        Layout.fillWidth: true // 親の幅いっぱいに広がるように要求
    }
    Rectangle {
        width: 80; height: 30; color: "blue"
    }
    Rectangle {
        width: 80; height: 30; color: "green"
        Layout.preferredWidth: 100 // 希望する幅
        Layout.preferredHeight: 40 // 希望する高さ
    }
    

    }
    ``
    レイアウトを使用する場合、子アイテムは
    Layoutアタッチプロパティ(Layout.fillWidth,Layout.preferredWidth,Layout.alignment` など)を使ってレイアウトマネージャーにどのように配置されたいかを伝えます。レイアウトはアンカーと組み合わせて使用することもできます。

2.4 Zオーダー (スタッキング)

同じ親を持つ複数のアイテムは、QMLコードで定義された順番に描画されます。後に定義されたアイテムほど手前に描画されます。これはZオーダーと呼ばれ、手前にあるアイテムが奥にあるアイテムを隠す可能性があります。

“`qml
Rectangle { // 親
width: 200; height: 200; color: “lightgray”

Rectangle { // 奥に描画される
    width: 100; height: 100; color: "red"; x: 20; y: 20
}
Rectangle { // 手前に描画される
    width: 100; height: 100; color: "blue"; x: 80; y: 80
}

}
``
この例では、青い矩形が赤い矩形の上に描画されます。アイテムの
zプロパティを設定することで、このデフォルトの描画順序を上書きすることもできます。z` の値が大きいほど手前に描画されます。

“`qml
Rectangle { // 親
width: 200; height: 200; color: “lightgray”

Rectangle { // これを手前に描画したい
    width: 100; height: 100; color: "red"; x: 20; y: 20; z: 1
}
Rectangle { // これを奥に描画したい
    width: 100; height: 100; color: "blue"; x: 80; y: 80; z: 0
}

}
``zのデフォルト値は0です。この例では、赤い矩形のz` を1に設定したことで、青い矩形(z=0)より手前に描画されます。

第3章:インタラクション (Interaction)

UIは単に情報を表示するだけでなく、ユーザーからの入力(マウス、タッチ、キーボードなど)に応答する必要があります。Qt Quickでは、これらのインタラクションを簡単に処理するためのメカニズムが提供されています。

3.1 マウス/タッチイベント

視覚要素自体は直接マウスやタッチイベントを受け取りません。イベントを受け取るためには、通常 MouseArea アイテムを使用し、それをイベントを受け取りたいアイテムの上に配置します。

“`qml
Rectangle {
id: clickableRect
width: 150; height: 100; color: “steelblue”

Text {
    text: "Click Me!"
    anchors.centerIn: parent
    color: "white"
}

MouseArea {
    anchors.fill: parent // 親(clickableRect)全体をマウス領域とする
    onClicked: { // クリック時に実行されるシグナルハンドラ
        console.log("Rectangle clicked!")
        clickableRect.color = "darkblue"
    }
    onPressed: { // 押下時に実行される
        console.log("Mouse pressed")
        clickableRect.scale = 0.95 // 少し縮小
    }
    onReleased: { // 離された時に実行される
        console.log("Mouse released")
        clickableRect.scale = 1.0 // 元のサイズに戻す
    }
    onEntered: { // マウスカーソルが領域に入った時
        console.log("Mouse entered")
    }
    onExited: { // マウスカーソルが領域から出た時
        console.log("Mouse exited")
    }
    onPositionChanged: { // マウスカーソルの位置が変わった時
        // mouse.x, mouse.y で位置情報が取得できる
    }
    // longPressed, doubleClicked など、他のシグナルハンドラもある
}

}
``MouseAreaは様々なマウスイベントに対応するシグナルハンドラ(onClicked,onPressed,onReleased,onEntered,onExitedなど)を提供します。これらのハンドラ内で、イベント発生時に実行したいJavaScriptコードを記述します。ハンドラ内では、mouse` というプロパティを通じてイベントの詳細情報(クリックの種類、位置など)にアクセスできます。

モバイル環境ではタッチイベントが中心となりますが、MouseArea は多くのタッチジェスチャー(タップ、プレスなど)にも対応します。より高度なジェスチャー(パン、ピンチなど)を検出するには、TapHandler, PinchHandler, PanHandler などの専用のハンドラを使用します。これらはQt Quick Inputモジュール (import QtQuick.Input 1.15) で提供されます。

3.2 キーボードイベント

キーボードイベントを受け取るには、アイテムがフォーカスを持っている必要があります。Item やその派生クラスは focus プロパティを持っており、これを true に設定することでフォーカスを受け取れるようになります。

“`qml
Rectangle {
id: inputRect
width: 200; height: 100; color: “lightgreen”
focus: true // このアイテムがフォーカスを持つように要求する

Text {
    id: statusText
    text: "Press keys"
    anchors.centerIn: parent
    color: "black"
}

// キーボードイベントハンドラ
Keys.onPressed: { // キーが押された時
    statusText.text = "Key pressed: " + event.key + " (" + event.text + ")"
    // event.key にはキーコード, event.text には文字が入る (修飾キー以外)
    if (event.key === Qt.Key_Space) { // スペースキーが押されたら
        color = "darkgreen"
        event.accepted = true // イベントを消費(他のハンドラに伝播しない)
    }
}
Keys.onReleased: { // キーが離された時
    statusText.text = "Key released: " + event.key
    if (event.key === Qt.Key_Space) {
         color = "lightgreen"
    }
}
// Keys.onReleased: は KeyNavigation.moveLeft, KeyNavigation.moveRight など、
// カーソルキーによるフォーカス移動などのアタッチプロパティもある。

}
``Keys.onPressedKeys.onReleasedなどのアタッチプロパティを使って、キーイベントハンドラを定義します。ハンドラ内ではeventオブジェクトを通じてキーコード、文字、修飾キーの状態などの情報にアクセスできます。event.accepted = true` とすることで、そのイベントが処理済みであることを示し、他のハンドラやシステムのデフォルト処理への伝播を止められます。

3.3 シグナルとスロット (Connections)

前の章でも触れましたが、シグナルとスロットはQMLの重要なインタラクションメカニズムです。オブジェクトは特定のイベントでシグナルを発行し、他のオブジェクトはシグナルハンドラ(例: onClicked, onColorChanged)や Connections アイテムを使ってそのシグナルを受け取り、指定したスロット(JavaScriptコードブロックや別のオブジェクトのメソッド呼び出し)を実行します。

Connections アイテムは、特定のオブジェクトのシグナルを、別のオブジェクトのプロパティ内でなく、独立した場所で受け取りたい場合に便利です。これにより、シグナル発行元とシグナルハンドラをより疎結合にできます。

“`qml
Rectangle {
id: sourceRect
width: 100; height: 100; color: “red”
// このRectangleにはクリックされたことを示すカスタムシグナルがあると仮定する
// 例: signal clicked(real x, real y)
// 実際には、MouseAreaなどの既存のアイテムを使うのが一般的
}

Rectangle {
id: targetRect
width: 100; height: 100; color: “blue”
x: 150; y: 0
}

// sourceRectからのシグナルを受け取る
Connections {
target: sourceRect // このオブジェクトのシグナルを受け取る

// sourceRectが持つであろう 'clicked' シグナルに対応するハンドラ
// 実際には、MouseAreaをsourceRectの子として配置し、MouseAreaのonClickedを使うことが多い
// 例として、もしsourceRectにonCustomSignalというシグナルハンドラがあれば:
// onCustomSignal: {
//     console.log("Custom signal received!")
//     targetRect.color = "green"
// }

// よく使うのは、プロパティ変更シグナル
onColorChanged: { // sourceRect.color が変更されたら
    console.log("sourceRect color changed to: " + sourceRect.color)
    // targetRectの色をsourceRectの色と同じにする
    targetRect.color = sourceRect.color
}

}

MouseArea {
anchors.fill: sourceRect
onClicked: {
// MouseAreaのonClicked内で、sourceRectのcolorプロパティを変更する
sourceRect.color = sourceRect.color === “red” ? “orange” : “red”
}
}
``Connectionsアイテムはtargetプロパティでどのオブジェクトからのシグナルを受け取るかを指定します。そして、そのターゲットオブジェクトが発行するシグナルに対応するハンドラ(例:on)を記述します。これは、MouseAreaonClicked` のように、シグナル発行元のオブジェクト内にハンドラを記述する代替手段となります。特に、シグナル発行元とハンドラが別のQMLファイルで定義されている場合などに有効です。

3.4 プロパティバインディングの詳細

プロパティバインディングは、UIの同期を保つための非常に強力なメカニズムです。値が変更されたときに自動的に他の値も更新されるため、手動でイベントハンドラを書いてプロパティを更新する手間が省けます。

“`qml
Rectangle {
id: masterRect
width: 200; height: 100; color: “red”

MouseArea {
    anchors.fill: parent
    onClicked: {
        masterRect.width = masterRect.width + 20 > 400 ? 200 : masterRect.width + 20 // クリックで幅を増やし、上限でリセット
    }
}

}

Rectangle {
id: slaveRect
height: 100
color: “blue”
x: masterRect.x + masterRect.width + 10 // masterRectの右側にバインド
y: masterRect.y

// 幅はmasterRectの幅の半分にバインド
width: masterRect.width / 2

}
``
この例では、
slaveRectxプロパティとwidthプロパティがmasterRectのプロパティにバインドされています。masterRectをクリックしてそのwidthが変更されると、slaveRectxwidth` も自動的に再計算され、UIが更新されます。

バインディングは単にプロパティを参照するだけでなく、JavaScript式を含むことができます (masterRect.width / 2)。より複雑な計算や条件に基づいて値を決定することも可能です。

“`qml
Text {
width: 200
text: mySlider.value > 50 ? “Value is high” : “Value is low” // スライダーの値に応じてテキストを変更
color: mySlider.value > 50 ? “green” : “red”
anchors.centerIn: parent
}

Slider { // Qt Quick Controls 2のSliderを使用する場合 (import QtQuick.Controls 2.15)
id: mySlider
width: 150
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: text.bottom + 20
from: 0
to: 100
value: 30
}
``
この例では、
Textアイテムのtextcolorプロパティが、Sliderアイテムのvalueプロパティにバインドされています。スライダーを動かしてvalue` が変更されると、テキストの内容と色もリアルタイムに更新されます。

プロパティバインディングは宣言的UI開発の中核であり、UI要素間の依存関係を明確かつ簡潔に記述できるため、非常に強力です。

第4章:動的なUI (Dynamic UI)

実際のアプリケーションでは、静的な要素だけでなく、データに基づいて内容が変化したり、リストやグリッドのように多数の似たような要素を表示したりする動的なUIが必要になります。Qt Quickはこのような要求に応えるための機能を提供しています。

4.1 リスト/グリッドビュー (ListView, GridView)

データのリストやグリッドを表示するために、ListViewGridView といったアイテムが用意されています。これらは、データモデル、ビュー、デリゲートの3つの主要な要素から構成されます。

  • モデル (Model):
    表示するデータのソースです。単純なQMLの ListModel、JavaScript配列、またはC++で実装されたモデルを使用できます。
    ListModel は、QML内で単純な構造化データ(リストまたはリストのリスト)を定義するのに便利です。各項目は ListElement で表現され、複数のプロパティを持つことができます。

    qml
    ListModel {
    id: fruitModel
    ListElement { name: "Apple"; color: "Red" }
    ListElement { name: "Banana"; color: "Yellow" }
    ListElement { name: "Cherry"; color: "Red" }
    ListElement { name: "Grape"; color: "Purple" }
    ListElement { name: "Orange"; color: "Orange" }
    }

  • デリゲート (Delegate):
    モデル内の個々のデータ項目が、ビュー内でどのように表示されるかを定義するQMLコンポーネントです。デリゲートは、モデルから提供される各項目のデータにアクセスできます。デリゲートコンポーネントは通常、インラインで定義されるか、別のQMLファイルとして定義されます。

    デリゲート内では、モデルの各項目のプロパティに model.<propertyName> の形式でアクセスできます。ListModel の場合、model.name, model.color のようになります。また、リスト内のインデックスは index プロパティでアクセスできます。

    “`qml
    // ListViewのdelegateプロパティに設定する
    Component { // または delegate: Rectangle { … } のようにインラインで定義
    Rectangle {
    width: listView.width // 親のListViewの幅を参照
    height: 40
    color: model.color // モデルのcolorプロパティを使用
    border.color: “gray”

        Text {
            text: model.name // モデルのnameプロパティを使用
            anchors.verticalCenter: parent.verticalCenter
            x: 10
            color: "white" // テキストの色は固定
        }
    
        MouseArea {
            anchors.fill: parent
            onClicked: {
                console.log("Clicked on item:", model.name, "at index:", index)
                // クリックされたアイテムのデータを操作することも可能
                // fruitModel.set(index, { color: "dark" + model.color }) // 例:色を変更
            }
        }
    }
    

    }
    “`

  • ビュー (View):
    モデルのデータをデリゲートを使って表示するアイテムです。ListView は垂直または水平方向に要素を並べ、GridView はグリッド状に要素を並べます。

    qml
    ListView {
    id: listView
    width: 200
    height: 200
    model: fruitModel // 上記で定義したモデルを設定
    delegate: Component { // デリゲートの定義
    Rectangle {
    width: listView.width // ListViewの幅いっぱいに
    height: 40
    color: model.color
    border.color: "gray"
    Text {
    text: model.name + " (" + index + ")" // nameとindexを表示
    anchors.verticalCenter: parent.verticalCenter
    x: 10
    }
    }
    }
    // orientation: ListView.Horizontal // 水平リストにすることも可能
    // spacing: 5 // アイテム間のスペース
    }

    GridView も同様に modeldelegate プロパティを持ちます。

    qml
    GridView {
    id: gridView
    width: 300
    height: 300
    model: fruitModel
    delegate: Component {
    Rectangle {
    width: 80 // 各グリッドアイテムのサイズはdelegate内で指定
    height: 80
    color: model.color
    Text {
    text: model.name
    anchors.centerIn: parent
    }
    }
    }
    cellWidth: 100 // グリッドのセルの幅
    cellHeight: 100 // グリッドのセルの高さ
    }

リストビューやグリッドビューは、大量のデータを効率的に表示するために、画面に表示されている部分のみをレンダリング(仮想化)します。スクロールはビューアイテムによって自動的に処理されます。

4.2 ローダー (Loader)

Loader アイテムは、別のQMLファイルやコンポーネントを動的に読み込んで表示するために使用されます。これにより、必要な時にのみUIの一部をロードしたり、実行時に表示するUIを切り替えたりすることができます。

“`qml
Rectangle {
width: 300; height: 300

Loader {
    id: pageLoader
    anchors.fill: parent
}

Column {
    anchors.bottom: parent.bottom
    anchors.horizontalCenter: parent.horizontalCenter
    spacing: 10

    // import QtQuick.Controls 2.15 が必要
    Button { text: "Load Page 1"; onClicked: pageLoader.source = "Page1.qml" }
    Button { text: "Load Page 2"; onClicked: pageLoader.source = "Page2.qml" }
    Button { text: "Unload"; onClicked: pageLoader.source = "" /* または source: undefined */ }
}

}
``
そして、
Page1.qmlPage2.qml` という別のQMLファイルを用意します。

“`qml
// Page1.qml
import QtQuick 2.15

Rectangle {
color: “lightyellow”
Text { text: “This is Page 1”; anchors.centerIn: parent }
}

// Page2.qml
import QtQuick 2.15

Rectangle {
color: “lightpink”
Text { text: “This is Page 2”; anchors.centerIn: parent }
}
``Loadersourceプロパティに読み込みたいQMLファイルのパスを指定することで、そのQMLファイルが表すコンポーネントがLoaderの領域内にインスタンス化されて表示されます。sourceを空文字列またはundefined` に設定すると、ロードされたコンポーネントは破棄されます。

LoadersourceComponent プロパティを使って、QMLファイルではなく、インラインで定義された Component をロードすることもできます。

4.3 スタックビュー (StackView)

複数の「ページ」や「画面」があり、それらをナビゲーション履歴に基づいてプッシュ/ポップしながら切り替えたい場合、StackView (import QtQuick.Controls 2.15 モジュールの一部) が非常に便利です。これは、モバイルアプリケーションのような画面遷移パターンを簡単に実現できます。

“`qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Window {
width: 400; height: 600
visible: true
title: “StackView Example”

StackView {
    id: stackView
    anchors.fill: parent
    initialItem: Page1 { } // 最初に表示するアイテム(コンポーネントまたはファイルパス)
}

}
``Page1.qml`:

“`qml
// Page1.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Page { // StackViewで使うにはPageが便利
// StackView.onStatusChanged: console.log(“Page1 status:”, StackView.status) // アタッチプロパティ

header: ApplicationHeader { title: "Page 1" } // Qt Quick Controls 2のヘッダーなど

ColumnLayout {
    anchors.fill: parent
    padding: 10
    spacing: 10

    Label { text: "Welcome to Page 1!" }

    Button {
        text: "Go to Page 2"
        onClicked: {
            stackView.push("Page2.qml") // StackViewにPage2をプッシュ
        }
    }
}

}
``Page2.qml`:

“`qml
// Page2.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Page {
header: ApplicationHeader { title: “Page 2” }

ColumnLayout {
    anchors.fill: parent
    padding: 10
    spacing: 10

    Label { text: "You are on Page 2." }

    Button {
        text: "Go back"
        onClicked: {
            stackView.pop() // StackViewから現在のページをポップ(戻る)
        }
    }
    Button {
        text: "Go to Page 3 (Replace)"
        onClicked: {
            stackView.replace(Qt.resolvedUrl("Page3.qml")) // 現在のページを置き換える
        }
    }
}

}
``StackViewpush(),pop(),replace(),clear()` などのメソッドを提供し、ページのナビゲーション履歴を管理しながら、スムーズな画面遷移アニメーション(プラットフォームに応じたデフォルトアニメーションが適用される)を提供します。これは、マルチスクリーンアプリケーションを構築する際に非常に有用です。

第5章:コンポーネントとモジュール化

QMLは、UI要素を再利用可能なコンポーネントとして定義し、アプリケーションをモジュール化する強力な仕組みを提供します。これにより、コードの重複を避け、保守性を高め、開発効率を向上させることができます。

5.1 QMLコンポーネントの作成

最も簡単なQMLコンポーネントは、独立した .qml ファイルです。ファイル名はコンポーネントの型名となります(例: Button.qmlButton という型のコンポーネントを定義)。

“`qml
// MyButton.qml
import QtQuick 2.15

Rectangle {
id: buttonRect
width: 120; height: 40
color: “lightblue”
radius: 5

signal clicked() // このコンポーネント独自のシグナルを定義

Text {
    id: buttonText
    anchors.centerIn: parent
    text: "Click" // デフォルトのテキスト
    color: "black"
    font.pointSize: 14
}

MouseArea {
    anchors.fill: parent
    onClicked: {
        buttonRect.clicked() // MouseAreaのonClickedシグナルを、コンポーネントのclickedシグナルに繋ぐ
    }
}

// 外部から設定できるようにプロパティを公開
property string text: buttonText.text // Textアイテムのtextプロパティを、コンポーネントのtextプロパティとして公開

// クリック時の色変更アニメーション(後述のAnimationの概念を使用)
states: [
    State {
        name: "pressed"
        when: mouseArea.pressed // MouseAreaが押されている状態
        PropertyChanges { target: buttonRect; color: "darkblue" }
    }
]
transitions: Transition {
    from: ""; to: "pressed"
    reversible: true // 逆方向のアニメーションも有効
    ColorAnimation { duration: 100 } // 色の変化にアニメーションを適用
}

}
``
この
MyButton.qmlファイルは、MyButton` という名前の新しいQMLコンポーネントを定義しています。他のQMLファイルからこのコンポーネントを使用するには、そのファイルと同じディレクトリに置くか、モジュールとしてインポートします。

5.2 再利用可能なUI要素として利用

作成したコンポーネントは、標準のQMLアイテムと同じように使用できます。

“`qml
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
width: 600; height: 400
visible: true
title: “Custom Button Example”

Column { // import QtQuick.Layouts が無くても使える基本的なColumn
    anchors.centerIn: parent
    spacing: 20

    MyButton { // MyButton.qml で定義したコンポーネントを使用
        text: "OK" // 公開した 'text' プロパティを設定
        onClicked: { // コンポーネントで定義した 'clicked' シグナルを受け取る
            console.log("OK button clicked!")
        }
    }

    MyButton {
        text: "Cancel"
        onClicked: {
            console.log("Cancel button clicked!")
        }
    }
}

}
“`
このように、カスタムコンポーネントは、プロパティを設定したり、シグナルハンドラを定義したりすることで、柔軟に再利用できます。

5.3 プロパティエイリアス

コンポーネントの内部にあるアイテムのプロパティを、コンポーネント自身のプロパティとして公開したい場合があります。上の MyButton の例では、Text アイテムの text プロパティを buttonRecttext プロパティとして公開しました。これは property alias を使って行います。

“`qml
// MyButton.qml (再掲)
Rectangle {
id: buttonRect
// … 他のプロパティ …

Text {
    id: buttonText
    // ... 他のプロパティ ...
    text: "Default" // 内部的なデフォルト値
}

// property alias text: buttonText.text // これにより、外部から MyButton { text: "..." } のように設定可能になる

// colorプロパティも公開したい場合
// property alias buttonColor: buttonRect.color // 外部からは MyButton { buttonColor: "..." } のように設定

}
``property alias <新しいプロパティ名>: <参照したいオブジェクトのID>.<プロパティ名>` の形式で定義します。これにより、内部オブジェクトのプロパティへの参照が、コンポーネント自身のプロパティとして公開されます。エイリアスは読み書き可能であり、バインディングの対象にもなります。

5.4 コンポーネントのインスタンス化

QMLコンポーネントは、QMLファイルとして定義されるだけでなく、Component 要素を使ってQMLコード内でインラインで定義することもできます。これは、ListViewGridViewdelegate プロパティに設定する場合によく使われます。

qml
ListView {
// ...
delegate: Component { // インラインで無名コンポーネントを定義
Rectangle {
// ... delegate item definition ...
}
}
}

Component 要素自身は視覚的な表示を持ちませんが、その中に定義されたルートアイテムが、インスタンス化されたときに表示される実際のアイテムとなります。Loader アイテムの sourceComponent プロパティに Component オブジェクトをセットすることで、プログラム的にコンポーネントをインスタンス化することも可能です。

5.5 モジュールとインポート

アプリケーションが大きくなるにつれて、QMLファイルを整理し、関連するコンポーネントをグループ化する必要が出てきます。Qt Quickでは、ディレクトリ構造と import 文を使ってこれを実現します。

  • ディレクトリ構造:
    QMLファイルはディレクトリに整理できます。例えば、カスタムコンポーネントを components ディレクトリに入れるなど。

    myapp/
    ├── main.qml
    ├── components/
    │ ├── MyButton.qml
    │ └── MyHeader.qml
    └── pages/
    ├── HomePage.qml
    └── SettingsPage.qml

  • インポート:
    別のディレクトリにあるQMLコンポーネントを使用するには、import 文でそのディレクトリを指定します。

    “`qml
    // main.qml
    import QtQuick 2.15
    import QtQuick.Window 2.15
    import “./components” // components ディレクトリをインポート

    Window {
    // …
    MyButton { // components/MyButton.qml が使えるようになる
    // …
    }
    }
    ``
    相対パス (
    ./components) や、Qtのリソースシステムパス (qrc:///components)、またはQMLモジュール名 (MyModule 1.0`) でインポートできます。

QMLモジュールは、関連するQMLファイル、JavaScriptファイル、画像などをまとめてパッケージ化し、バージョン管理や名前空間を提供するための仕組みです。アプリケーション固有の再利用可能なライブラリや、配布可能なコンポーネント集を作成する際に使用されます。QMLモジュールについては、より進んだトピックとなるため、ここでは概要のみの紹介に留めますが、大規模なアプリケーション開発においては不可欠な要素となります。

第6章:アニメーションとトランジション (Animation & Transitions)

ユーザーにリッチで応答性の高い体験を提供するために、UI要素にアニメーションやトランジションを適用することは非常に効果的です。Qt Quickは強力で柔軟なアニメーションフレームワークを提供します。

6.1 基本的なアニメーション (PropertyAnimation)

最も一般的なアニメーションは、アイテムのプロパティの値を時間経過とともに滑らかに変化させるものです。これには PropertyAnimation が使用されます。

“`qml
Rectangle {
id: animRect
width: 100; height: 100; color: “red”
x: 50; y: 50

MouseArea {
    anchors.fill: parent
    onClicked: {
        // Rectangleを新しい位置にアニメーションさせる
        moveAnimation.start()
    }
}

PropertyAnimation {
    id: moveAnimation
    target: animRect // アニメーション対象のアイテム
    property: "x" // アニメーションさせるプロパティ名 (文字列で指定)
    to: 200 // アニメーション終了時の目標値

    // 他のプロパティも同時にアニメーションさせたい場合は、別途PropertyAnimationを定義するか、SequentialAnimation/ParallelAnimationを使用
    // PropertyAnimation { target: animRect; property: "y"; to: 200; ... }

    duration: 500 // アニメーションの長さ (ミリ秒)
    // easing.type: Easing.InOutQuad // アニメーションの補間曲線
    // running: true // 自動的に開始するか (通常はfalseにしてstart()で開始)
}

}
``PropertyAnimationは、targetアイテムのpropertyを、現在の値からtoに指定された値まで、durationの時間でアニメーションさせます。fromプロパティを指定して開始値を明示することもできます。easing.typeを設定することで、アニメーションの変化の仕方を調整できます(例:Easing.InOutQuad` でゆっくり始まりゆっくり終わる)。

6.2 アニメーションの種類

Qt Quickには、様々なデータ型に対応した専用のアニメーション要素があります。これらは PropertyAnimation の特殊化版と考えることができます。

  • NumberAnimation: 数値プロパティのアニメーション (x, y, width, height, opacity など)
  • ColorAnimation: 色プロパティのアニメーション (color, borderColor など)
  • RotationAnimation: rotation プロパティのアニメーション
  • ScaleAnimation: scale プロパティのアニメーション
  • Vector3dAnimation: 3次元ベクトル (x, y, z または x, y, z, w の形式) プロパティのアニメーション
  • UniformAnimator: x, y, width, height など、複数の数値プロパティを同時にアニメーションさせる場合に便利

“`qml
Rectangle {
id: multiAnimRect
width: 100; height: 100; color: “red”
x: 50; y: 50
rotation: 0

MouseArea {
    anchors.fill: parent
    onClicked: {
        // 色と回転を同時にアニメーション
        colorAnim.start()
        rotationAnim.start()
    }
}

ColorAnimation {
    id: colorAnim
    target: multiAnimRect
    property: "color"
    to: multiAnimRect.color === "red" ? "blue" : "red" // クリックごとに色を切り替え
    duration: 400
}

RotationAnimation {
    id: rotationAnim
    target: multiAnimRect
    property: "rotation"
    to: multiAnimRect.rotation + 90 // 現在の角度から90度回転
    duration: 400
    // direction: RotationAnimation.Clockwise // 回転方向の指定
}

}
“`

6.3 トランジション (Transition): 状態変化時のアニメーション

Qt Quickの強力な機能の一つに「状態 (States)」があります。アイテムは複数の定義された状態を持つことができ、各状態ではプロパティの値や子アイテムの可視性などが異なります。アイテムの状態が変化する際に、プロパティ値の変化をアニメーションさせるのが Transition です。

“`qml
Rectangle {
id: stateRect
width: 200; height: 100
color: “red”

// 状態の定義
states: [
    State {
        name: "expanded" // 状態名
        PropertyChanges { target: stateRect; width: 300; color: "blue" } // expanded状態でのプロパティ値
    },
    State {
        name: "collapsed" // 状態名
        PropertyChanges { target: stateRect; width: 100; color: "red" } // collapsed状態でのプロパティ値
    }
]

// 状態遷移時のアニメーション(トランジション)
transitions: [
    Transition {
        // from と to を省略すると、全ての状態遷移に適用
        // from: "collapsed"; to: "expanded" // collapsedからexpandedへの遷移時のみ適用
        // from: "*"; to: "expanded" // どの状態からexpandedへの遷移時でも適用

        // 状態変化で影響を受けるプロパティに対するアニメーション
        PropertyAnimation { property: "width"; duration: 300 }
        PropertyAnimation { property: "color"; duration: 300 }

        // 別のアイテムのプロパティも同時にアニメーション可能
        // PropertyAnimation { target: anotherItem; property: "opacity"; to: 0.5; duration: 300 }
    }
]

MouseArea {
    anchors.fill: parent
    onClicked: {
        // クリックで状態を切り替える
        stateRect.state = stateRect.state === "expanded" ? "collapsed" : "expanded"
    }
}

// 初期状態を設定 (省略した場合、最初のStateが初期状態となることが多い)
state: "collapsed"

}
``statesプロパティには、Stateオブジェクトのリストを定義します。各Stateオブジェクトは、その状態名と、その状態で変化させたいプロパティ値を記述したPropertyChangesリストを持ちます。PropertyChanges` には、ターゲットアイテムと、そのターゲットアイテムのプロパティの目標値を記述します。

transitions プロパティには、Transition オブジェクトのリストを定義します。各 Transition オブジェクトは、どの状態遷移(from, to プロパティで指定)に適用されるかと、その遷移時に実行するアニメーション(animations プロパティまたは子要素として定義)を持ちます。PropertyAnimation を定義することで、状態変化によって値が変わるプロパティに対して自動的にアニメーションが適用されます。

状態とトランジションは、UIの複雑な状態変化を管理し、それらを滑らかに見せるための非常に強力なパターンです。

6.4 並列/逐次アニメーション (SequentialAnimation, ParallelAnimation)

複数のアニメーションをまとめて実行したい場合、SequentialAnimation または ParallelAnimation を使用します。

  • SequentialAnimation: 子として定義されたアニメーションを順番に実行します。
  • ParallelAnimation: 子として定義されたアニメーションを同時に実行します。

“`qml
Rectangle {
id: combinedAnimRect
width: 50; height: 50; color: “purple”
x: 0; y: 0

MouseArea {
    anchors.fill: parent
    onClicked: {
        // 逐次アニメーションを開始
        sequentialAnim.start()
    }
}

SequentialAnimation { // 順番に実行されるアニメーション
    id: sequentialAnim
    loops: 2 // 2回繰り返す

    // 1. 右に移動
    PropertyAnimation { target: combinedAnimRect; property: "x"; to: 200; duration: 500 }
    // 2. 下に移動
    PropertyAnimation { target: combinedAnimRect; property: "y"; to: 200; duration: 500 }
    // 3. 色を変える (これはすぐに実行される)
    PropertyAction { target: combinedAnimRect; property: "color"; value: "orange" }
    // 4. 幅と高さを同時にアニメーション (並列)
    ParallelAnimation {
        NumberAnimation { target: combinedAnimRect; property: "width"; to: 100; duration: 300 }
        NumberAnimation { target: combinedAnimRect; property: "height"; to: 100; duration: 300 }
    }
    // 5. 開始位置に戻る
    PropertyAnimation { target: combinedAnimRect; property: "x"; to: 0; duration: 500 }
    PropertyAnimation { target: combinedAnimRect; property: "y"; to: 0; duration: 500 }
}

// ParallelAnimation の例
// ParallelAnimation {
//     id: parallelAnim
//     PropertyAnimation { target: combinedAnimRect; property: "x"; to: 200; duration: 1000 }
//     PropertyAnimation { target: combinedAnimRect; property: "y"; to: 200; duration: 1000 }
//     ColorAnimation { target: combinedAnimRect; property: "color"; to: "green"; duration: 1000 }
// }

}
``SequentialAnimationParallelAnimationは、より複雑なアニメーションシーケンスや組み合わせを実現するために、他のアニメーション要素(PropertyAnimation,ScriptAction,PauseAnimation` など)を子として持つことができます。

第7章:QMLとC++の連携

QMLはUIの記述に優れていますが、複雑なビジネスロジック、データ処理、システムリソースへのアクセス、既存のC++ライブラリの利用など、パフォーマンスが要求される処理にはC++を使用するのが適しています。Qt QuickはQMLとC++をシームレスに連携させる強力な仕組みを提供しており、これこそがQt Quickがエンタープライズ級のアプリケーション開発にも使用される理由の一つです。

7.1 なぜC++と連携するのか?

  • パフォーマンス: 計算量の多い処理や、タイトなループ、低レベルの操作はC++の方が高速です。
  • 既存コードの活用: 既に開発済みのC++ライブラリやモジュールをUIレイヤーから利用できます。
  • システムリソースアクセス: ファイルシステム、ネットワーク、データベース、ハードウェアデバイスなど、システムレベルの機能へのアクセスはC++で行うのが一般的です。
  • 複雑なロジック: 大規模なアプリケーションにおける複雑なビジネスロジックやデータモデルは、C++で構造化して実装する方が管理しやすい場合があります。
  • プラットフォーム固有の機能: 特定のプラットフォームに依存する機能(OSレベルのAPI呼び出しなど)はC++で実装します。

7.2 QMLからC++関数を呼び出す

C++で定義されたクラスのパブリックなスロット(slots または Q_INVOKABLE マクロ付きのパブリックメソッド)は、QMLから呼び出すことができます。

まず、C++側でQMLから呼び出したいメソッドを持つクラスを定義します。

“`cpp
// mycppobject.h

ifndef MYCPPOBJECT_H

define MYCPPOBJECT_H

include

include

class MyCppObject : public QObject
{
Q_OBJECT // Qtのメタオブジェクトシステムに必要なマクロ
public:
explicit MyCppObject(QObject *parent = nullptr);

// QMLから呼び出し可能なメソッド (Q_INVOKABLE または slots)
Q_INVOKABLE void greet(const QString &name);
Q_INVOKABLE int add(int a, int b);

// QMLからアクセス可能なプロパティ
Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged)
QString status() const { return m_status; }
void setStatus(const QString &status);

signals:
// QMLで受け取れるシグナル
void statusChanged(const QString &status);
void dataReady(const QVariantList &data); // QVariantListなどもやり取り可能

private:
QString m_status;
};

endif // MYCPPOBJECT_H

“`

“`cpp
// mycppobject.cpp

include “mycppobject.h”

MyCppObject::MyCppObject(QObject *parent) : QObject(parent)
{
m_status = “Idle”;
}

void MyCppObject::greet(const QString &name)
{
qDebug() << “Hello from C++,” << name;
setStatus(“Greeted ” + name); // プロパティ変更 -> statusChanged シグナル発行
}

int MyCppObject::add(int a, int b)
{
qDebug() << “Adding” << a << “and” << b;
return a + b;
}

void MyCppObject::setStatus(const QString &status)
{
if (m_status == status)
return;
m_status = status;
emit statusChanged(m_status); // プロパティ変更シグナルを発行
}
“`

次に、C++アプリケーションのメイン部分で、このオブジェクトをQMLコンテキストに公開します。

“`cpp
// main.cpp

include

include

include // QQmlContext を使用するために必要

include “mycppobject.h”

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);

QQmlApplicationEngine engine;

// C++オブジェクトをQMLコンテキストに登録
MyCppObject myObject;
engine.rootContext()->setContextProperty("myCppObject", &myObject);
// QML側からは 'myCppObject' という名前でこのオブジェクトにアクセスできる

engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); // QMLファイルをロード

if (engine.rootObjects().isEmpty()) {
    return -1;
}

return app.exec();

}
``
Qt CreatorでQt Quick Applicationプロジェクトを作成し、上記のファイルをプロジェクトに追加・編集します。
main.qmlをリソースファイル (.qrc`) に追加することも忘れずに行います。

QML側からは、setContextProperty で指定した名前 (myCppObject) を使って、このC++オブジェクトにアクセスし、メソッドを呼び出したり、プロパティを読み書きしたりできます。

“`qml
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15 // Buttonを使用

Window {
width: 400; height: 300
visible: true
title: “QML C++ Integration”

ColumnLayout {
    anchors.centerIn: parent
    spacing: 10

    Button {
        text: "Call C++ Greet"
        onClicked: {
            // C++オブジェクトの greet メソッドを呼び出す
            myCppObject.greet("World from QML")
        }
    }

    Button {
        text: "Call C++ Add"
        onClicked: {
            // C++オブジェクトの add メソッドを呼び出し、戻り値を受け取る
            var result = myCppObject.add(5, 7)
            console.log("C++ add result:", result)
        }
    }

    Text {
        // C++オブジェクトの status プロパティをバインドして表示
        text: "Status: " + myCppObject.status
        // myCppObject.status が変更されると、このテキストも自動更新される
    }

    Text {
        id: signalStatusText
        text: "Signal Status: Initial"
    }

    // C++オブジェクトのシグナルを受け取る
    Connections {
        target: myCppObject // シグナルを受け取るC++オブジェクト

        // C++の statusChanged シグナルに対応するハンドラ
        onStatusChanged: { // シグナルの引数を受け取る
            signalStatusText.text = "Signal Status: " + status // 引数名(status)でアクセス
            console.log("QML received statusChanged signal:", status)
        }

        // C++の dataReady シグナルに対応するハンドラ
        // onDataReady: { // data という引数名で受け取る
        //     console.log("QML received dataReady signal:", data)
        //     // リストビューのモデルとして使用するなど
        // }
    }
}

}
“`
QMLからC++のメソッドを呼び出す際は、JavaScriptの関数呼び出しのように記述します。C++のプロパティにアクセスする際も、QMLのプロパティと同様にアクセスできます。プロパティバインディングもC++のプロパティに対して機能します。

7.3 C++からQMLオブジェクトを操作する

C++側からQMLオブジェクトのプロパティを変更したり、メソッドを呼び出したりすることも可能です。これは、C++のロジックに基づいてUIを更新する場合などに使用します。

まず、C++側でQMLオブジェクトにアクセスするための準備をします。QQmlApplicationEngine::rootObjects() メソッドで、ルートQMLファイル内のトップレベルオブジェクトのリストを取得できます。または、QMLオブジェクトにアクセスしやすい id を割り当てておきます。

“`qml
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
id: mainWindow // C++からアクセスしやすいようにIDを付ける
width: 400; height: 300
visible: true
title: “QML Manipulation from C++”

Rectangle {
    id: targetRect // C++から操作したいオブジェクト
    width: 100; height: 100; color: "cyan"
    anchors.centerIn: parent

    // C++から呼び出し可能なQMLメソッドを定義
    function changeColor(newColor) {
        targetRect.color = newColor
    }
}

}
``
C++側では、
QQmlApplicationEngine::findChild(“targetRect”)のように、idを使ってオブジェクトを検索できます。(id` はQMLローダーによってオブジェクト名として設定されることが多いため)。より頑丈な方法としては、ルートオブジェクトをたどるか、特定の型で検索する方法があります。

“`cpp
// main.cpp (一部変更)

include

include

include // 定期的にQMLを操作するために使用

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral(“qrc:/main.qml”)));

if (engine.rootObjects().isEmpty()) {
    return -1;
}

// ルートオブジェクトを取得
QObject *rootObject = engine.rootObjects().first();

// IDでQMLオブジェクトを検索
QObject *targetRect = rootObject->findChild<QObject*>("targetRect");

if (targetRect) {
    // QMLオブジェクトのプロパティを変更
    targetRect->setProperty("color", "magenta");

    // QMLメソッドを呼び出す (QMetaObject::invokeMethod)
    QMetaObject::invokeMethod(targetRect, "changeColor",
                              Q_ARG(QVariant, "yellow")); // 引数を QVariant で渡す

    // 定期的に色を変えるタイマーを設定
    QTimer *timer = new QTimer(&app);
    QObject::connect(timer, &QTimer::timeout, [&]() {
        static bool isCyan = true;
        if (targetRect) {
            QMetaObject::invokeMethod(targetRect, "changeColor",
                                      Q_ARG(QVariant, isCyan ? "cyan" : "darkmagenta"));
            isCyan = !isCyan;
        }
    });
    timer->start(1000); // 1秒ごとに実行
} else {
    qWarning("targetRect not found in QML");
}

return app.exec();

}
``QObject::setProperty(propertyName, value)を使うことで、QMLオブジェクトのプロパティの値を変更できます。QMetaObject::invokeMethod(object, methodName, …)を使うことで、QMLオブジェクトで定義されたJavaScript関数やQ_INVOKABLE付きのC++メソッド(そのQMLオブジェクトがC++で実装された場合)を呼び出すことができます。引数はQ_ARGマクロを使ってQVariant` でラップして渡します。

7.4 C++オブジェクトをQMLに公開する (まとめ)

C++オブジェクトをQMLから利用可能にするには、いくつかの方法があります。

  1. コンテキストプロパティとして公開:
    QQmlEngine::rootContext()->setContextProperty("name", cppObjectPtr)
    特定の名前(name)で、QMLのルートコンテキストからどこからでもアクセスできるようになります。グローバルなオブジェクトや、アプリケーション全体で共有されるべきオブジェクトに適しています。

  2. QMLのアイテムのプロパティとして設定:
    qml
    MyItem {
    id: myQmlItem
    cppObject: myCppObject // C++オブジェクトをプロパティに設定
    }

    C++側で、MyItem クラス(またはベースクラス)に Q_PROPERTY としてC++オブジェクト型(または QObject*)のプロパティを追加しておき、MyQmlItem* qmlItem = rootObject->findChild<MyQmlItem*>("myQmlItem"); のように取得したQMLオブジェクトのプロパティにC++オブジェクトのポインタを設定します。これにより、そのQMLアイテムとその子孫からC++オブジェクトにアクセスできます。

  3. QML型として登録:
    qmlRegisterType<MyCppItem>("com.mycompany.qmlitems", 1, 0, "MyCppItem");
    C++で QQuickItem (視覚的なアイテム)や QObject (非視覚的な要素、ロジックなど)を継承したクラスを定義し、qmlRegisterType 関数でQMLエンジンに新しい型として登録します。QML側からは import com.mycompany.qmlitems 1.0 のようにインポートし、<MyCppItem { ... }> のように他のQMLアイテムと同様に使用できるようになります。これは、再利用可能なカスタムUI要素や、複雑なネイティブ要素をQMLから扱えるようにする場合に最適な方法です。

C++のクラス設計においては、QMLからアクセスしたいプロパティは Q_PROPERTY マクロで定義し、メソッドは Q_INVOKABLE マクロまたは slots キーワードでマークすることが重要です。また、プロパティが変更された際には、関連する NOTIFY シグナルを発行することで、QML側でのプロパティバインディングが正しく機能するようになります。

7.5 C++モデルをQMLビューに連携する

リストビュー (ListView, GridView) やテーブルビュー (TableView) のモデルとして、C++で実装されたモデルクラスを使用することができます。これにより、複雑なデータ構造や大量のデータを効率的にQMLに表示できます。C++モデルは通常、Qtのモデル/ビューフレームワークの一部として、QAbstractListModel, QAbstractTableModel などのクラスを継承して実装されます。

C++モデルの実装は詳細なトピックですが、基本的には以下の要素が必要です。
* QAbstractListModel などを継承したクラス
* roleNames(): QML側でデータにアクセスする際に使用する役割名(プロパティ名のようなもの)を定義
* rowCount(): モデル内の項目数を返す
* data(): 指定されたインデックスと役割に対応するデータを返す
* データの変更を通知するためのシグナル(dataChanged, rowsInserted, rowsRemoved など)

C++モデルを実装したら、QQmlContext::setContextProperty() または他の方法でQMLに公開し、ListView などの model プロパティに設定します。

“`cpp
// main.cpp (モデルを公開する部分)

include “mycustommodel.h” // QAbstractListModel を継承したクラス

// … main 関数内 …
MyCustomModel *myModel = new MyCustomModel(&app);
// モデルにデータを追加するなど…
engine.rootContext()->setContextProperty(“myCustomModel”, myModel);
// …
“`

“`qml
// main.qml (モデルを使用する部分)
ListView {
width: 200; height: 300
model: myCustomModel // C++で公開したモデルを使用
delegate: Component {
Rectangle {
width: parent.width; height: 50
color: “lightgray”
border.color: “black”

        Text {
            // C++モデルの roleNames で定義した役割名を使ってデータにアクセス
            text: model.name + " (" + model.value + ")"
            anchors.centerIn: parent
        }
    }
}

}
``
QMLのデリゲート内では、
model.` の形式でC++モデルのデータにアクセスできます。C++モデル内でデータが変更され、適切なシグナルが発行されると、QMLビューは自動的に更新されます。

第8章:実践的なUI開発のヒント

これまで学んだ基礎知識を基に、より実践的なUI開発を行う上でのヒントをいくつか紹介します。

8.1 パフォーマンスに関する考慮事項

Qt Quickはハードウェアアクセラレーションを活用し高性能な描画を提供しますが、QMLコードの書き方によってはパフォーマンスが低下する可能性があります。

  • 過度なプロパティバインディングを避ける: 複雑なJavaScript式を含む多数のバインディングは、頻繁に評価されるとパフォーマンスに影響を与える可能性があります。静的な値や、変更頻度が低い値にはバインディングを使わない、複雑な計算はC++で行うなどを検討します。
  • リストビュー/グリッドビューのデリゲート: デリゲートはリスト内の各項目に対してインスタンス化されます。デリゲートの構造はできるだけシンプルに保ち、不要なアイテムやバインディングを含めないようにします。高価な操作(画像ロードなど)は、デリゲート内で行うのではなく、モデル側で準備することを検討します。表示されていないアイテムのアニメーションや更新を停止する設定(ListView::populate, ListView::cacheBuffer など)もパフォーマンスに影響します。
  • アニメーションの最適化: 同時に実行するアニメーションの数を抑え、不要なアニメーションは停止します。ハードウェアアクセラレーションが有効になっていることを確認します。
  • 画像のロード: 大量の画像を一度にロードしないようにします。Image アイテムの cache プロパティや、モデルと連携した遅延ロードなどを検討します。
  • JavaScriptの利用: QMLのシグナルハンドラなどで実行されるJavaScriptコードは、UIスレッドと同じスレッドで実行されます。時間のかかるJavaScript処理はUIの応答性を低下させるため、可能な限りC++に移すか、ワーカーキューなどを使用して別スレッドで実行することを検討します。
  • Zオーダーと可視性: z プロパティは描画順序を変えるために内部的にレイヤーを作成するなどコストがかかる場合があります。多用は避けるか、必要に応じて使用します。非表示のアイテムは visible: false にすることで描画コストを削減できます。

8.2 デバッグ手法 (QML Debugger)

Qt Creatorには強力なQMLデバッガーが統合されています。これを利用することで、QMLコードにブレークポイントを設定したり、変数の値を検査したり、コールスタックを確認したり、プロパティバインディングの評価状況を確認したりできます。

デバッガーを効果的に使用するには、プロジェクト設定でQMLデバッグを有効にする必要があります。Qt Creatorでプロジェクトを開き、「プロジェクト」モードで実行設定を選択し、「Run」設定の「Debugger」タブでQMLデバッグのオプションをチェックします。

QMLデバッガー以外にも、console.log() を使用した出力や、視覚的な問題についてはQt Creatorの「QML Visual Editor」やアプリケーション実行中の「QML Inspector」ツール(Ctrl+Alt+Shift+P で表示される場合がある)もデバッグに役立ちます。

8.3 ローカリゼーション (国際化対応)

複数の言語に対応する(ローカリゼーション)ためには、アプリケーション内のテキストを翻訳可能にする必要があります。QMLでは、qsTr() 関数を使ってテキストを翻訳対象としてマークします。

“`qml
Text {
text: qsTr(“Hello, World!”) // 翻訳対象としてマーク
}

Button {
text: qsTr(“OK”)
}
``
Qtの国際化ツール(
lupdate,lrelease)を使って、これらのマークされたテキストを抽出、翻訳ファイル (.ts) を生成し、翻訳を行い、バイナリ形式の翻訳ファイル (.qm) にコンパイルします。アプリケーションのC++側で、これらの.qm` ファイルをロードし、現在の言語を設定することで、UIのテキストが自動的に翻訳されます。

8.4 異なる画面サイズ/解像度への対応

様々なデバイスやウィンドウサイズでUIが適切に表示されるように、レスポンシブデザインを考慮する必要があります。

  • アンカーとレイアウト: 第2章で説明したように、アンカー (Anchors) とレイアウト (Layouts) は、絶対座標ではなく相対的な位置関係や比率に基づいてアイテムを配置するため、ウィンドウサイズが変更されてもUIの構造が維持されやすいです。これらを積極的に使用します。
  • サイズの柔軟性: width: parent.width のように親のサイズに合わせたり、Layout.fillWidth: true のようにレイアウト内で利用可能なスペースを埋めるようにしたり、パーセンテージベースのサイズ指定を検討したりします。特定の固定サイズが必要な場合は、Layout.preferredWidth, Layout.minimumWidth, Layout.maximumWidth などでレイアウトマネージャーにヒントを与えます。
  • 解像度: Qt Quickは通常、物理ピクセルではなくデバイス非依存ピクセル(DIPs)で描画されるため、異なる画面解像度でもUI要素の物理的なサイズがほぼ一定に保たれます。ただし、画像などのビットマップリソースについては、高解像度ディスプレイで鮮明に表示するために、適切な解像度のアセット(例: @2x サフィックス付きの画像ファイル)を用意することを検討します。
  • 状態 (State) の活用: 異なる画面サイズや向き(ポートレート/ランドスケープ)に応じてUIのレイアウトや要素の表示/非表示を切り替えたい場合があります。これを実現するために、特定の条件(例: when: parent.width > 600)に基づいて状態を切り替え、各状態内でプロパティを変更したり、異なるレイアウトを使用したりする方法が有効です。

まとめと次のステップ

この記事では、QMLとQt Quickを使ったUI開発の基礎を幅広く学びました。Qt Quickの魅力である宣言的なUI記述、プロパティバインディング、豊富なビジュアル要素とインタラクションメカニズム、動的なUI構築、コンポーネント化、アニメーション、そしてC++との強力な連携について、コード例を交えながら解説しました。

  • QMLはUIを宣言的に記述する言語であり、Qt Quickはそれをレンダリングするフレームワークです。
  • Item は全てのアイテムの基本であり、Rectangle, Image, Text などが基本的な視覚要素です。
  • アンカー (Anchors) やレイアウト (Layouts) を使って、柔軟でレスポンシブなUI配置を行います。
  • MouseArea, Keys, Connections を使ってユーザーインタラクションを処理します。
  • ListView, GridView とモデル/デリゲートを使って動的なリストやグリッドを表示します。
  • LoaderStackView を使って動的にUIの一部をロードしたり、画面遷移を管理したりします。
  • QMLファイルや Component を使って再利用可能なUIコンポーネントを作成し、property alias でプロパティを公開します。
  • PropertyAnimationTransition, State を使ってアニメーションや滑らかな状態遷移を実現します。
  • C++オブジェクトをQMLに公開したり、QMLからC++のメソッドやプロパティにアクセスしたりすることで、QMLのUI能力とC++の処理能力を組み合わせて活用できます。

これで、Qt Quickを使ったUI開発の基本的なツールキットを手にしたことになります。しかし、Qt Quickの世界はさらに広く深く、多くの機能や応用例が存在します。

次のステップとして、以下のトピックについて学習を深めることをお勧めします。

  • Qt Quick Controls 2: Qt Quick標準のUIコントロールセットです。ボタン、テキストフィールド、スライダー、ダイアログなど、一般的なUI要素をプラットフォームネイティブまたはカスタムスタイルで提供します。実際のアプリケーション開発ではこれらのコントロールを利用することがほとんどです。
  • QML Modules: カスタムコンポーネントを構造化し、再利用可能なライブラリとして管理する方法。
  • Qt Quick Shape: SVGパスなどを使って複雑なベクトル図形を描画する。
  • Qt Quick Effects: シェーダーベースの視覚効果を適用する。
  • Qt Quick 3D: QML内で3Dコンテンツを統合する。
  • Qt Quick Scene Graph: Qt Quickの内部レンダリングメカニズムを理解し、カスタムグラフィックスレンダリングを行う。
  • QML Profiler: パフォーマンス問題を特定するためのツール。
  • QMLとJavaScriptの連携: より高度なJavaScriptの利用、WorkerScriptを使った別スレッドでの処理など。
  • C++とQMLの高度な連携: モデル/ビュー/デリゲートパターン、カスタムC++アイテムの作成、マルチスレッド処理など。

これらのトピックについて学ぶには、以下のリソースが非常に役立ちます。

  • Qt公式ドキュメント: 最も正確で包括的な情報源です。特に、Qt Quickのドキュメント、QML Reference、各QMLアイテムクラスのドキュメントは必携です。
  • Qt Examples: Qtのインストールに含まれる豊富なサンプルコード。様々な機能の実装例を見ることができます。
  • Qt Creatorチュートリアル: Qt Creatorのヘルプメニューにあるチュートリアルは、プロジェクト作成から基本的な機能までを学ぶのに適しています。
  • 書籍やオンラインコース: Qt QuickやQMLに特化した学習リソースも多数存在します。

Qt Quickは、あなたの創造性を表現し、魅力的で高性能なクロスプラットフォームUIを構築するための強力なツールです。この記事が、その旅の出発点となり、あなたがQt Quick開発の楽しさと可能性を体験するきっかけとなれば幸いです。

Happy Coding with QML and Qt Quick!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール