C
C++コードとQMLの統合
概要
Qt Quick Ultralite アプリケーションでは、ビジネスロジック、ハードウェアとのインタフェー ス、他のソフトウェアモジュールとの連携に C++ コードを使用しています。アプリケーションの C++ 部分は QML プレゼンテーションレイヤーと相互作用します。そのため、C++クラスやその関数、プロパティ、シグナルはQMLコードに公開されます。
インターフェースヘッダ
アプリケーションには、QMLに公開するコンポーネントを記述したインターフェースヘッダーを含めることができます。これらは通常の C++ ヘッダファイルであり、InterfaceFiles.filesプロパティを用いて QmlProject に登録されます。
注意: 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> から直接派生している場合、プラグマ・シングルトンでマークされ、グローバルにインスタンスが利用できます。
次の例では、シングルトンの使い方を示します:
#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には、ユーザー定義の演算子==を与えなければなりません。
このようにして定義されたプロパティは組み込みプロパティのように振る舞います。特に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 updateEvent 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()
}列挙型
エクスポートされたクラス宣言の中の public enum 宣言は QML enum に変換されます。
以下のコードは public enum の使い方を示しています:
struct MyStates : public Qul::Singleton<MyStates>
{
enum State { On, Off, Broken };
};
Item {
property MyStates.State state: MyStates.On
}C++ から QML への型マッピング
| C++型 | QML型 |
|---|---|
| ブール | ブール |
| 積分 (char, short, int, long, ...) | int |
| 浮動小数点 (float, double) | 実数 |
| std::string (UTF-8エンコーディングを想定) | 文字列 |
| T*(TはQul::Object | 一致するコンポーネント型 |
| エクスポートされたクラスの enum | 一致する列挙型 |
| ListProperty<T*>。Qul::Object | list<T> |
| TがQML基本型に存在する場合、ListProperty<T>となる | リスト |
注意: 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
では、このSingletonオブジェクトを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の対応するテキストが変更されます。