このページでは

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 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()
}

列挙型

エクスポートされたクラス宣言内のパブリック列挙型の宣言は、QMLの列挙型に変換されます。

以下のコードは、パブリック列挙型の使用方法を示しています:

struct MyStates : public Qul::Singleton<MyStates>
{
    enum State { On, Off, Broken };
};


Item {
    property MyStates.State state: MyStates.On
}

C++ から QML への型マッピング

C++の型QML型
boolbool
整数型 (char, short, int, long, ...)int
浮動小数点 (float, double)実数
std::string(UTF-8 エンコーディングと仮定)文字列
T*(TはQul::Object対応するコンポーネント型
エクスポートされたクラス内のenum一致する列挙型
ListProperty<T*>(Tが以下から派生している場合)Qul::Objectlist<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ライセンスの下で利用可能です。
詳細はこちら。