C
C++コードとQMLの統合
概要
Qt Quick のUltraliteアプリケーションでは、ビジネスロジックの実装、ハードウェアとの連携、および他のソフトウェアモジュールとの連携にC++コードが使用されます。アプリケーションのC++部分は、QMLのプレゼンテーション層と連携します。そのため、C++クラスおよびその関数、プロパティ、シグナルは、QMLコードから利用可能になります。
インターフェースヘッダー
アプリケーションには、QMLに対して公開すべきコンポーネントを記述するインターフェースヘッダーを含めることができます。これらは通常のC++ヘッダーファイルであり、QmlProjectのInterfaceFiles.filesプロパティを使用して登録されます。
注: QMLとインターフェースしないコードを保護するには、インターフェースファイル内で#ifndef __clang__ マクロを使用してください 。qmlinterfacegenerator は、保護されたコードを処理しません。
QmlProjectでインターフェースヘッダーファイルを登録すると、ビルドに次のような影響があります:
qmlinterfacegeneratorツールがヘッダーに対して実行され、1つ以上のQMLインターフェースファイルが生成されます- プロジェクトのQMLコードを翻訳する際、生成されたファイルが
qmltocppに渡されます
これらの手順により、QMLコードでC++インターフェースを利用できるようになります。
C++ API のエクスポート
コンポーネント
クラスおよび構造体の宣言は、C++の機能をQMLに公開するための構成要素です。エクスポートされた各C++クラス宣言は、同名のQMLコンポーネントを定義します。
以下の例は、コンポーネントの使用方法を示しています:
#include <qul/object.h> #include <qul/property.h> #include <qul/signal.h> struct BigState : public Qul::Object { Qul::Property<int> bigness; int feed(int amount); Qul::Signal<void()> gotFood; };
Item { BigState { id: bigState bigness: 61 onGotFood: bigness = 99 } Component.onCompleted: bigState.feed(3) }
qmlinterfacegenerator に認識されるためには、宣言されたクラスは
- は、少なくとも間接的にQul::Object
- デフォルトコンストラクタを持つクラスでなければならない
一部の基底クラスには特別な意味があります。詳細については、「シングルトンと モデル」を参照してください。
シングルトン
クラスがQul::Singleton<Itself>から直接派生している場合、そのクラスにはpragma Singletonが指定され、そのインスタンスはグローバルに利用可能になります。
次の例は、シングルトンの使用方法を示しています:
#include <qul/singleton.h> struct MyApi : public Qul::Singleton<MyApi> { void start(); };
Item { Component.onCompleted: MyApi.start() }
モデル
このクラスは、リストモデルをエクスポートするために、Qul::ListModel<T> から直接派生させることができます。詳細については、Qul::ListModel のドキュメントを参照してください。
関数
エクスポートされたクラス宣言内の、オーバーロードされていないすべてのパブリックメンバ関数は、QML で利用可能です。関数の引数型および戻り値型は、それぞれ対応する QML の型にマッピングされます。
以下の例は、関数の使用方法を示しています:
struct DirLookup : public Qul::Object { Qul::Property<std::string> basePath; int lookup(const std::string &path); }; struct Simulator : public Qul::Singleton<Simulator> { void run(DirLookup *lookup); };
Item { DirLookup { id: look basePath: "/objects" } Component.onCompleted: Simulator.run(look) }
プロパティ
エクスポートされたクラス宣言内のパブリックフィールドは、そのフィールドの型が `Qul::Property<T>` である場合、コンポーネントのプロパティとなります。ここで、T はプロパティの C++ 型を表し、対応する QML 型にマッピングされます。
注: 組み込みの比較演算子を持たない型 T については、すべて ユーザー定義の operator== を定義する必要があります。
この方法で定義されたプロパティは、組み込みのプロパティと同様に動作します。特に、QML でバインディングを割り当てることができ、他のバインディングのデータソースとして使用することができます。
以下の例は、プロパティの使用方法を示しています。C++でプロパティを定義します:
struct MyData : public Qul::Object { Qul::Property<int> val; void update(int x) { // can get and set property values from C++ val.setValue(x); } };
そして、QMLで次のように使用します:
Item { Item { // can bind QML property to exported property x: mydata_x.val color: "red" width: 50 height: 50 } MyData { id: mydata_x val: 100 } MyData { id: mydata_width // can bind exported property val: parent.width } Component.onCompleted: { mydata_x.update(200); console.log(mydata_width.val); } }
注: Qul::Property::setValue を呼び出しても request Qt Quick Ultralite engine update。Event queue は、実行中のQMLアニメーションやタイマーがない場合に必要です。再描画は、以下に示すようにevent queue を使用することでトリガーできます。
struct MySingleton : public Qul::Singleton<MySingleton> { Qul::Property<int> val; // function that is called only from the C++ code void update(int value); }; class MyEventQueue : public Qul::EventQueue<int> { void onEvent(const int &value) override { // set property value in the event handler MySingleton::instance().val.setValue(value); } }; void MySingleton::update(int value) { static MyEventQueue myEventQueue; myEventQueue.postEvent(value); }
グループ化されたプロパティ
プロパティは次のようにグループ化できます:
struct MyObject : public Qul::Object { struct Grouped { Qul::Property<int> val1; Qul::Property<int> val2; Qul::ListProperty<int> list1; }; Grouped group; };
その後、QMLでは次のように使用できます:
Item { MyObject { group.val1: 42 group.val2: 43 } }
グループ化は、プロパティを構造体またはクラス S の中に配置し、エクスポートされるクラス内に型 S のフィールドを設けることで行われます。型 S 自体は、Qul::Object から派生してはなりません。グループ内のプロパティとして公開されるのは、型Qul::Property のパブリックフィールドのみです。グループはプロパティ専用であり、シグナル、列挙型、関数を含めることはできません。
プロパティの一覧
エクスポートされたクラスのパブリックメンバーは、Qul::ListProperty<T>型である場合、コンポーネントリストプロパティとなります。詳細については、Qul::ListProperty のドキュメントを参照してください。
シグナル
Qul::Signal<Fn> 型のパブリックフィールドは、QML コンポーネント上のシグナルに変換されます。
テンプレート引数 `Fn ` は、シグナルのパラメータ型を記述する関数型でなければなりません。通常通り、これらの型は対応する QML 型にマッピングされます。同様に、`Fn ` で使用されるパラメータ名は、QML シグナルのパラメータ名となります。
以下のコードは、シグナルの使用方法を示しています:
struct MyItem : public Qul::Object { Qul::Signal<void(int sigValue)> triggered; void callTriggered() { triggered(42); } };
Item { MyItem { id: myitem onTriggered: console.log(sigValue); } Component.onCompleted: myitem.callTriggered() }
列挙型
エクスポートされたクラス宣言内のパブリック列挙型の宣言は、QMLの列挙型に変換されます。
以下のコードは、パブリック列挙型の使用方法を示しています:
struct MyStates : public Qul::Singleton<MyStates> { enum State { On, Off, Broken }; };
Item { property MyStates.State state: MyStates.On }
C++ から QML への型マッピング
| C++の型 | QML型 |
|---|---|
| bool | bool |
| 整数型 (char, short, int, long, ...) | int |
| 浮動小数点 (float, double) | 実数 |
| std::string (UTF-8 エンコーディングとみなす) | 文字列 |
| T*(TはQul::Object | 対応するコンポーネント型 |
| エクスポートされたクラス内のenum | 一致する列挙型 |
| ListProperty<T*>(Tが以下から派生している場合)Qul::Object | list<T> から派生する場合 |
| TがQML基本型に含まれる場合、ListProperty<T> | list |
注: std::string への変換は 、動的なメモリ割り当てを伴います。
割り込みハンドラから QML へのデータ転送
以下の手法は、Qt Quick Ultraliteに存在するいくつかのAPIやメカニズムを組み合わせたものです。その結果、アプリケーションの任意の場所(割り込みハンドラを含む)から、簡単かつ効率的にデータを転送することが可能になります。
この例では、QMLのシングルトンパターンを使用してデータをC++に公開するデータ転送について説明しますが、この手法はオブジェクトやモデルにも同様に適用可能です。
注: interrupt_handler のサンプルでは 、ここで説明した手法を実演しています。このサンプルは、examples ディレクトリ内にあります。
必要なものは?
まず、データペイロードとして使用するイベント型を定義することから始めます。
struct HMIInputEvent { enum Type { KeyPress, KeyRelease }; int keyCode; Type type; };
次に、C++ から QML へのインターフェース(例:Qul::Singleton )を実装します。これを `Qul::EventQueue ` と組み合わせることで、イベントを受信した際にプロパティの値を変更したり、シグナルを発行したりすることができます。
例:
#include <qul/singleton.h> #include <qul/eventqueue.h> struct HMIInput : Qul::Singleton<HMIInput>, Qul::EventQueue<HMIInputEvent> { Qul::Signal<void(int key)> pressed; Qul::Signal<void(int key)> released; void onEvent(const HMIInputEvent &inputEvent) override { if (inputEvent.type == HMIInputEvent::KeyPress) { pressed(inputEvent.keyCode); } else if (inputEvent.type == HMIInputEvent::KeyRelease) { released(inputEvent.keyCode); } } };
これによりシングルトン・オブジェクトが作成され、イベントを受信した際にpressed およびreleased シグナルを発火します。HMIInputEvent
それでは、QMLでこのシングルトンオブジェクトを活用してみましょう
import QtQuick 2.15 Rectangle { Row { spacing: 10 Text { id: operation } Text { id: keyCode } } HMIInput.onPressed: { operation.text = "pressed" keyCode.text = key } HMIInput.onReleased: { operation.text = "released" keyCode.text = key } }
この時点で、実装は、たとえば割り込みハンドラなどから EventQueue にイベントを投稿してデータを転送する準備が整いました。
例:
static void keyEventInterruptHandler() { static HMI_StateTypeDef keyBuffer; if (BSP_HMI_GetState(&keyBuffer) != HMI_OK) { return; } HMIInput::instance().postEventFromInterrupt( HMIInputEvent{keyBuffer.code, keyBuffer.pressed ? HMIInputEvent::KeyPress : HMIInputEvent::KeyRelease}); }
注: ベアメタルプラットフォーム向けのデフォルトの キュー実装では 、イベントキューへの投稿を行うライターは1つしか存在できません。詳細については、Qul::EventQueue の説明を参照してください。
これで、keyEventInterruptHandler() が呼び出されるたびに、QML 内の対応するテキストが変更されるようになります。
特定のQtライセンスの下で利用可能です。
詳細はこちら。