C++型の属性をQMLに公開する

QML は C++ コードで定義された機能を簡単に拡張することができます。QML エンジンはQt メタオブジェクトシステムと密接に統合されているため、QObject 由来のクラスやQ_GADGET の型が適切に公開している機能であれば、 QML のコードからアクセスすることができます。このため、C++のデータや関数にQMLから直接アクセスすることが可能です。

QMLエンジンはメタオブジェクトシステムを通してQObject インスタンスをイントロスペクトする機能を持っています。つまり、どのようなQMLコードでも、QObject-derived classのインスタンスの以下のメンバにアクセスすることができます:

  • プロパティ
  • メソッド(パブリックスロットであるか、Q_INVOKABLE のフラグがついていれば)
  • シグナル

(さらに、Q_ENUM で宣言された列挙型も使用可能です。詳細はQMLとC++のデータ型変換を参照してください)。

一般的には、QObject から派生したクラスがQML の型システムに登録されているかどうかに関係なく、QML からアクセス可能です。しかし、あるクラスをメソッドのパラメータやプロパティとして使ったり、 列挙型の1つをこのような形で使ったりする場合など、エンジンが追加の型情報に アクセスする必要があるような使い方をする場合には、そのクラスを登録する必要が あるかもしれません。登録された型のみがコンパイル時に解析されるため、QMLで使用するすべての型について登録することをお勧めします。

Q_GADGET 。これらの型は既知の共通ベースから派生しておらず、自動的に利用できるようにすることができないため、登録が必要です。登録しなければ、そのプロパティやメソッドにアクセスすることはできません。

DEPENDENCIESオプションを使用してqt_add_qml_module呼び出しに依存関係を追加することで、異なるモジュールからのC++型を自分のモジュールで利用できるようにすることができます。例えば、QtQuick に依存することで、QMLで公開されているC++型がQColor をメソッドの引数や戻り値として利用できるようになります。QtQuickQColor値型 カラーとして公開します。このような依存関係は実行時に自動的に推測されるかも知れませんが、これに頼るべきでは ありません。

また、この文書で扱われている重要な概念の多くは、チュートリアル「Writing QML Extensions with C++」で示されていることに注意してください。

C++やさまざまなQML統合方法についての詳細は、C++とQML統合の概要ページを参照してください。

データ型の取り扱いと所有権

C++からQMLに転送されるデータは、プロパティ値、メソッドのパラメータや戻り値、シグナルのパラメータ値など、QMLエンジンがサポートしている型でなければなりません。

デフォルトでは、Qt C++ の多くの型がサポートされており、QML から利用する際には自動的に適切な型に変換されます。さらに、QMLの型システムに登録されているC++クラスも、適切に登録されていれば、その列挙型もデータ型として使用することができます。詳しくはQML と C++ 間のデータ型変換を参照してください。

さらに、C++からQMLへデータを転送する際には、データ所有権のルールが考慮され ます。詳しくはデータ所有権を参照してください。

プロパティの公開

QObject から派生したクラスでは、Q_PROPERTY()マクロを使ってプロパティを指定することができます。プロパティは、関連する読み取り関数とオプションの書き込み関数を持つクラス・データ・メンバです。

QObject-derived またはQ_GADGET クラスのすべてのプロパティは QML からアクセス可能です。

例えば、author プロパティを持つMessage クラスを以下に示します。Q_PROPERTY マクロ呼び出しで指定されたように、このプロパティはauthor() メソッドで読み込み可能で、setAuthor() メソッドで書き込み可能です:

注: Q_PROPERTY の型にtypedefusingを使用すると moc が混乱するので、使用しないでください。これにより、特定の型比較に失敗することがある。

代わりに

using FooEnum = Foo::Enum;

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(FooEnum enum READ enum WRITE setEnum NOTIFY enumChanged)
};

型を直接参照する:

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Foo::Enum enum READ enum WRITE setEnum NOTIFY enumChanged)
};

Message を利用できるようにするには、C++ でQML_ELEMENT を使い、CMake でqt_add_qml_module を使う必要があります。

class Message : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
    void setAuthor(const QString &a)
    {
        if (a != m_author) {
            m_author = a;
            emit authorChanged();
        }
    }

    QString author() const
    {
        return m_author;
    }

signals:
    void authorChanged();

private:
    QString m_author;
};

Message のインスタンスをMyItem.qml というファイルに必須プロパティとして渡すことで、利用できるようになります:

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQuickView view;
    Message msg;
    view.setInitialProperties({{"msg", &msg}});
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}

そうすれば、author プロパティをMyItem.qml から読み取ることができる:

// MyItem.qml
import QtQuick

Text {
    required property Message msg

    width: 100; height: 100
    text: msg.author    // invokes Message::author() to get this value

    Component.onCompleted: {
        msg.author = "Jonah"  // invokes Message::setAuthor()
    }
}

QMLとの相互運用性を最大にするために、書き込み可能なプロパティには、プロパティの値が変更されたときに発信されるNOTIFYシグナルを関連付ける べきです。これはQMLの本質的な機能であり、依存関係にあるプロパティの値が変更されると自動的に更新されることで、プロパティ間の関係を強制するものです。

上記の例では、author プロパティに関連する NOTIFY シグナルは、Q_PROPERTY() マクロ呼び出しで指定されたように、authorChanged です。つまり、Message::setAuthor() で作者が変更されたときのように、このシグナルが発せられるたびに、author プロパティに関係するすべてのバインディングを更新しなければならないことが QML エンジンに通知され、その結果、エンジンはMessage::author() を再度呼び出すことによってtext プロパティを更新します。

author プロパティが書き込み可能であるにもかかわらず、関連する NOTIFY シグナルがない場合、text の値はMessage::author() によって返された初期値で初期化されますが、このプロパティに対する後の変更によって更新されることはありません。さらに、QML からこのプロパティにバインドしようとすると、エンジンから実行時警告が出ます。

注意: NOTIFY シグナルは<property>Changedという名前にすることを推奨します。<property> はプロパティの名前です。QML エンジンが生成するプロパティ変更シグナルハンドラは、関連する C++ シグナルの名前にかかわらず、常にon<Property>Changed という形式をとります。

Notify シグナル使用上の注意

ループや過剰な評価を防ぐため、開発者は、プロパティ値が実際に変更されたときにのみ、プロパティ変更シグナルが発信されるようにする必要があります。また、あるプロパティやプロパティ・グループの使用頻度が低い場合、複数のプロパティに同じ NOTIFY シグナルを使用することが許される。この場合、パフォーマンスが低下しないように注意する必要がある。

NOTIFY シグナルが存在すると、小さなオーバーヘッドが発生する。プロパティの値がオブジェクトの構築時に設定され、その後変更されない場合がある。最も一般的なケースは、グループ化されたプロパティを使用するタイプで、グループ化されたプロパティ・オブジェクトが一度割り当てられ、オブジェクトが削除されたときにのみ解放される場合です。このような場合、NOTIFYシグナルの代わりにCONSTANT属性をプロパティ宣言に追加することができる。

CONSTANT属性は、クラス・コンストラクタ内でのみ値が設定され、確定されるプロパティにのみ使用されるべきである。バインディングで使用したい他のすべてのプロパティは、代わりにNOTIFYシグナルを持つべきです。

オブジェクト型を持つプロパティ

オブジェクト型のプロパティは、そのオブジェクト型が QML の型システムに適切に登録されていれば、 QML からアクセスすることができます。

例えば、Message 型はMessageBody* 型のbody プロパティを持つかもしれません:

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged)
public:
    MessageBody* body() const;
    void setBody(MessageBody* body);
};

class MessageBody : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged)
// ...
}

Message 型がQMLの型システムに登録され、QMLのコードからオブジェクト 型として利用できるようになったとします:

Message {
    // ...
}

MessageBody 型も型システムに登録されていれば、MessageBodyMessagebody プロパティに代入することができます:

Message {
    body: MessageBody {
        text: "Hello, world!"
    }
}

オブジェクトリスト型を持つプロパティ

QObject から派生した型のリストを含むプロパティもQMLに公開することができます。しかし、この場合、QList<T>ではなく、QQmlListProperty をプロパティの型として使う必要があります。なぜなら、QListQObject から派生した型ではないため、リストが変更されたときのシグナルの通知など、Qt メタオブジェクトシステムを通して必要な QML プロパティの特性を提供することができないからです。

例えば、以下のMessageBoard クラスにはQQmlListProperty 型のmessages プロパティがあり、Message インスタンスのリストを格納しています:

class MessageBoard : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
    QQmlListProperty<Message> messages();

private:
    static void append_message(QQmlListProperty<Message> *list, Message *msg);

    QList<Message *> m_messages;
};

MessageBoard::messages()関数はQList<T>m_messages メンバからQQmlListProperty を作成し、QQmlListProperty コンストラクタが必要とする適切なリスト変更関数を渡して返すだけです:

QQmlListProperty<Message> MessageBoard::messages()
{
    return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
}

void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg)
{
    MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object);
    if (msg)
        msgBoard->m_messages.append(msg);
}

なお、QQmlListProperty のテンプレートクラス型(ここではMessage )は QML の型システムに登録されている必要があります。

グループ化されたプロパティ

読み取り専用のオブジェクト型プロパティは、グループ化されたプロパティとして QML コードからアクセスすることができます。グループ化されたプロパティは、QMLのコードからアクセスすることができます。

例えば、Message::author プロパティが単純な文字列ではなく、MessageAuthor タイプで、サブプロパティとしてnameemail があったとします:

class MessageAuthor : public QObject
{
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(QString email READ email WRITE setEmail)
public:
    ...
};

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageAuthor* author READ author)
public:
    Message(QObject *parent)
        : QObject(parent), m_author(new MessageAuthor(this))
    {
    }
    MessageAuthor *author() const {
        return m_author;
    }
private:
    MessageAuthor *m_author;
};

author プロパティは、QMLのグループ化されたプロパティ構文を使って、次のように記述することができます:

Message {
    author.name: "Alexandra"
    author.email: "alexandra@mail.com"
}

グループ化されたプロパティとして公開される型はオブジェクト型のプロパティとは異なり、グループ化されたプロパティは読み取り専用であり、構築時に親オブジェクトによって有効な値に初期化されます。グループ化されたプロパティのサブプロパティは、QML から変更することができますが、グループ化されたプロパティオブジェクト自体が変更されることはありません。このように、グループ化されたプロパティオブジェクトの寿命は、C++の親実装によっ て厳密に制御されるのに対し、オブジェクト型のプロパティは、QMLのコードによって 自由に生成・破棄することができます。

メソッドの公開(Qt スロットを含む)

QObject から派生した型のメソッドは、QML コードからアクセス可能です:

  • Q_INVOKABLE() マクロでフラグが付けられた public メソッド。
  • Qt のパブリックスロットであるメソッド

例えば、以下のMessageBoard クラスにはQ_INVOKABLE マクロでフラグが付けられたpostMessage() メソッドと、public スロットであるrefresh() メソッドがあります:

classMessageBoard :publicQObject
{ Q_OBJECT QML_ELEMENTpublic: Q_INVOKABLEboolpostMessage(constQStringメッセージの投稿        qDebug() << "Called the C++ method with" << msg;
       return true; }public slots:voidrefresh() { {スロットをリフレッシュします。        qDebug() << "Called the C++ slot";
    } };

MessageBoard のインスタンスがファイルMyItem.qml の必須プロパティとして設定されている場合、MyItem.qml は以下の例に示すように2つのメソッドを呼び出すことができる:

C++
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    MessageBoard msgBoard;
    QQuickView view;
    view.setInitialProperties({{"msgBoard", &msgBoard}});
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}
QML
// MyItem.qml
import QtQuick 2.0

Item {
    required property MessageBoard msgBoard

    width: 100; height: 100

    MouseArea {
        anchors.fill: parent
        onClicked: {
            var result = msgBoard.postMessage("Hello from QML")
            console.log("Result of postMessage():", result)
            msgBoard.refresh();
        }
    }
}

C++ のメソッドにQObject* 型のパラメータがある場合、QML からオブジェクトid またはオブジェクトを参照する JavaScriptvar の値を使ってパラメータ値を渡すことができます。

QML はオーバーロードされた C++ 関数の呼び出しに対応しています。同じ名前で異なる引数を持つ C++ 関数が複数存在する場合、提供された引数の数 と型に応じて、正しい関数が呼び出されます。

C++ メソッドから返された値は、QML の JavaScript 式からアクセスされた場合、JavaScript の値に変換されます。

C++メソッドとthisオブジェクト

あるオブジェクトからC++のメソッドを取り出し、それを別のオブジェクトから 呼び出したい場合があります。Example という QML モジュールの中で、次のような例を考えてみましょう:

C++
class Invokable : public QObject
{
    Q_OBJECT
    QML_ELEMENT
public:
    Invokable(QObject *parent = nullptr) : QObject(parent) {}

    Q_INVOKABLE void invoke() { qDebug() << "invoked on " << objectName(); }
};
QML
import QtQml
import Example

Invokable {
    objectName: "parent"
    property Invokable child: Invokable {}
    Component.onCompleted: child.invoke.call(this)
}

適切な main.cpp から QML コードをロードすると、"invoked on parent" と表示されるはずです。しかし、長年のバグのため、そうはなりません。歴史的に、C++ベースのメソッドの'this'オブジェクトはメソッドと不可分にバインドされています。既存のコードでこの動作を変更すると、多くの場所で 'this' オブジェクトが暗黙的に存在するため、微妙なエラーが発生します。Qt 6.5以降では、明示的に正しい動作を選択し、C++メソッドが'this'オブジェクトを受け入れるようにすることができます。そのためには、QMLドキュメントに以下のプラグマを追加してください:

pragma NativeMethodBehavior: AcceptThisObject

この行を追加することで、上記の例は期待通りに動作するようになります。

シグナルの公開

QObject から派生した型のパブリックシグナルはすべてQMLのコードからアクセス可能です。

QMLエンジンは、QObject から派生したタイプのシグナルを QMLから利用する場合、自動的にシグナルハンドラを生成します。シグナルハンドラは常にon<Signal>という名前になります。<Signal> はシグナルの名前で、最初の文字は大文字になります。シグナルから渡されるすべてのパラメータは、シグナルハンドラ内でパラメータ名を通して利用することができます。

例えば、MessageBoard クラスにnewMessagePosted() シグナルがあり、subject という1つのパラメータがあるとします:

class MessageBoard : public QObject
{
    Q_OBJECT
public:
   // ...
signals:
   void newMessagePosted(const QString &subject);
};

MessageBoard の型がQMLの型システムに登録されていれば、QMLで宣言されたMessageBoard オブジェクトはonNewMessagePosted というシグナルハンドラを使ってnewMessagePosted() シグナルを受け取り、subject パラメータの値を調べることができます:

MessageBoard {
    onNewMessagePosted: (subject)=> console.log("New message received:", subject)
}

プロパティの値やメソッドのパラメータと同様に、シグナルのパラメータも QMLエンジンがサポートしている型でなければなりません。(未登録の型を使用してもエラーは発生しませんが、ハンドラからパラメータ値にアクセスすることはできません)。

クラスは同じ名前のシグナルを複数持つことができますが、QMLシグナルとしてアクセ スできるのは最後のシグナルだけです。同じ名前で異なるパラメータを持つシグナルは区別できないことに注意してください。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.