C++クラスをQMLとして登録する

Register C++ classes as QML
C++ではQMLはQObjectをベースとした拡張クラスとみなされます。QObjectを継承した独自のクラスをQMLエンジンに登録することで、QMLコードからそのC++クラスを呼び出すことができます。

通常はmain()関数内のqmlファイルを読み込む前に「qmlRegisterType<C++クラス>("URL", "メジャーバージョン", "マイナーバージョン, "QML内で使用する名称");」という形で登録します。URLとバージョンはもっぱらimport文で用いられ、URLの決まりは特にないものの、javaパッケージと同じ方式で宣言するのが定石となっています。

ためしに、HelloクラスをQMLコンポーネントとして扱えるようにしてみましょう。まずはmain.cppにHelloクラスを宣言しているヘッダファイルをインクルードし、qmlRegisterTypeでQMLエンジンに登録するようにプログラムします。
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "hello.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    qmlRegisterType<Hello>("com.tnksoft", 1, 0, "Hello");

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}
QObjectを継承したHelloクラスの作成です。QObjectをインクルードし、クラス宣言の初めにQML関連の宣言をマクロ化した「Q_OBJECT」をセミコロンなしで記述します。
hello1.h
#ifndef HELLO_H
#define HELLO_H

#include <QObject>

class Hello : public QObject
{
    Q_OBJECT

public:
    Hello();
    virtual ~Hello();
}

#endif
このクラスにプロパティを実装してみます。プロパティは「Q_PROPERTY」マクロを使用し、「Q_PROPERTY([プロパティの型] [プロパティ名] READ [getter関数] WRITE [setter関数] NOTIFY [signal関数])」のような形式で、カンマでは区切らずに宣言します。READの項目は必須ですが、読み出し専用プロパティであればWRITEやNOTIFYは省略しても構いません。

型の宣言例
QML型プロパティ型
整数qint
浮動小数(real)qreal
浮動小数(double)qdouble
Booleanbool
文字列QString
日付QDate

クラスの宣言部にgetter関数とsetter関数の宣言を追加します。Qtでは関数の宣言にCamel記法(先頭は小文字)を使うのが通例です。NOTIFYで記述した関数名は「signal:」として宣言し、関数の実体は作成しない点に気を付けましょう。C++で記述したシグナルは、qmakeツールによって、コンパイル先のOSに応じたイベント関数として自動で生成されます。
hello2.h
class Hello : public QObject
{
    Q_OBJECT
    // textという名称のプロパティを宣言
    Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)

private:
    // メンバ変数
    //Qt 5.1以降ではこのようにMEMBERを用意するとgetter/setter関数が自動で作られる
    //Q_PROPERTY(QString text MEMBER _text NOTIFY textChanged)
    QString _text;

signals:
  // シグナルは宣言のみで実装はしない
    void textChanged();

public:
    Hello();
    virtual ~Hello();

    // READで宣言した関数を実装
    QString getText();
    // WRITEで宣言した関数を実装
    // 数値型であればconstや&は必要ない
    void setText(const QString &text);
};
続いて関数の実装です。コード例のように、シグナルを発生させるには、呼び出したい箇所で「emit [シグナル関数];」を呼び出します。
hello.cpp
#include "hello.h"
#include <QDebug>

Hello::Hello() : QObject()
{
}

Hello::~Hello()
{
}

void Hello::setText(const QString &text)
{
    // もしtextを使用しないのなら「Q_UNUSED(text)」をコードの先頭に記述する
    _text = text;
    // QMLへtext変更のシグナルを送信する
    emit textChanged();
}

QString Hello::getText()
{
    return _text;
}
QMLからアクセス可能な関数を実装するには、対象の関数の前に「Q_INVOKABLE」を付加します。
invoke.h
class Hello : public QObject
{
public:
    Q_INVOKABLE int execCalc(int a, int b);
};

int Hello::execCalc(int a, int b)
{
    return a + b;
}
QMLでの実装例です。「qmlRegisterType」で登録したURLとバージョンの組み合わせをimportして、Helloをアイテムの一部として追加します。
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import com.tnksoft 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello Cpp")

    Hello{
        id:h
        onTextChanged: {
            // emit textChanged();によって実行される
            console.log("Text has updated.")
        }
    }

    Component.onCompleted: {
        // Q_INVOKABLEの関数を実行
        t.text = h.execCalc(10, 5);
    }

    Text{
        id:t
    }

    TextInput{
        id:i
        y:10; width:100
        onTextChanged: {
            // プロパティへの設置により、Hello::setText()が呼び出される
            h.text = this.text;
        }
    }
}
2018/11/05