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ライセンスの下で利用可能です。
詳細はこちら。