C++からQMLオブジェクトを操作する
QML のオブジェクト型は、エンジン内部で実装されたものであれ、サードパーティが 定義したものであれ、すべてQObject から派生したものです。つまり、QML エンジンは QtMeta Object System を使って、任意の QML オブジェクト型を動的にインスタンス化し、生成されたオブジェクトを検査することができます。
これは、C++のコードからQMLオブジェクトを生成する際に便利で、視覚的にレンダリングできるQMLオブジェクトを表示したり、視覚的でないQMLオブジェクトのデータをC++アプリケーションに統合したりすることができます。一度作成された QML オブジェクトは、C++ から検査することで、プロパティの読み書きを行ったり、 メソッドを呼び出したり、シグナル通知を受け取ったりすることができます。
C++と様々なQML統合方法についての詳細は、C++とQML統合の概要ページをご参照ください。
C++ からの QML オブジェクトの読み込み
QMLドキュメントは、QQmlComponent またはQQuickView を使ってロードすることができます。QQmlComponent はQMLドキュメントをC++オブジェクトとしてロードし、C++コードから変更することができます。QQuickView も同様ですが、QQuickView はQWindow 由来のクラスであるため、ロードされたオブジェクトはビジュアル表示されます。QQuickView は一般的に、表示可能なQMLオブジェクトをアプリケーションのユーザーインターフェースに統合するために使用されます。
例えば、次のようなMyItem.qml
ファイルがあるとします:
import QtQuick Item { width: 100; height: 100 }
このQML文書は、QQmlComponent またはQQuickView を使って、次のようなC++コードで読み込むことができます。QQmlComponent を使うには、QQmlComponent::create() を呼び出してコンポーネントの新しいインスタンスを作成する必要があります。一方、QQuickView を使うと、自動的にコンポーネントのインスタンスが作成され、QQuickView::rootObject() からアクセスできるようになります:
// Using QQmlComponent QQmlEngine engine; QQmlComponent component(&engine, QUrl::fromLocalFile("MyItem.qml")); QObject *object = component.create(); ... delete object; | // Using QQuickView QQuickView view; view.setSource(QUrl::fromLocalFile("MyItem.qml")); view.show(); QObject *object = view.rootObject(); |
このobject
は、作成されたMyItem.qml
コンポーネントのインスタンスです。QObject::setProperty() またはQQmlProperty::write() を使用して、アイテムのプロパティを変更できます:
object->setProperty("width", 500); QQmlProperty(object, "width").write(500);
QObject::setProperty()
とQQmlProperty::write()
の違いは、後者がプロパティ値の設定に加えてバインディングの削除も行うことです。例えば、上記のwidth
の代入がheight
へのバインディングであったとします:
width: height
object->setProperty("width", 500)
呼び出しの後にItem
のheight
が変更された場合、バインディングは有効なままなので、width
は再度更新されます。しかし、QQmlProperty(object, "width").write(500)
の呼び出しの後にheight
が変更された場合、バインディングはもう存在しないので、width
は変更されません。
別の方法として、オブジェクトを実際の型にキャストし、コンパイル時に安全なメソッドを呼び出すこともできます。この場合、MyItem.qml
のベース・オブジェクトはItem であり、QQuickItem クラスによって定義されます:
QQuickItem *item = qobject_cast<QQuickItem*>(object); item->setWidth(500);
また、QMetaObject::invokeMethod()やQObject::connect()を使って、コンポーネント内で定義されたシグナルや呼び出しメソッドに接続することもできます。詳しくは後述のQMLメソッドの呼び出しと QMLシグナルへの接続を参照してください。
よく定義されたC++インターフェースを介したQMLオブジェクトへのアクセス
C++ から QML にアクセスするための最良の方法は、C++ でインターフェースを定義し、 QML からそのインターフェースにアクセスすることです。他の方法では、QMLのコードをリファクタリングすると、QMLとC++の相互作用が簡単に壊れてしまいます。また、QMLとC++の相互作用は、QMLを経由することで、ユーザやqmllintのようなツールの両方から、より簡単に推論することができます。C++からQMLにアクセスすると、外部のC++コードがあるQMLコンポーネントを変更し ていないことを手作業で確認しなければ理解できないQMLコードが生成されます。
QMLにやりとりをさせるには、まずC++のインターフェースを定義する必要があります:
class CppInterface : public QObject { Q_OBJECT QML_ELEMENT // ... };
QML主導のアプローチを用いると、このインターフェイスは2つの方法で操作することができます:
シングルトン
インターフェースにQML_SINGLETON マクロを追加することで、インターフェースをシングルトンとして登録し、すべてのコンポーネントに公開する方法があります。その後、単純なimport文でインターフェースが利用できるようになります:
import my.company.module Item { Component.onCompleted: { CppInterface.foo(); } }
単純にオブジェクトを渡すだけでは、プロパティを介して他のコンポーネントに明示的に渡すか、非限定アクセスを使用する、遅くて推奨されない方法を利用する必要があるためです。
初期プロパティ
もうひとつの方法は、QML_UNCREATABLE を使ってインターフェースを作成不可能なものとしてマークし、QQmlComponent::createWithInitialProperties()を使ってルートQMLコンポーネントに供給し、QML側で必須プロパティを指定する方法です。
ルートコンポーネントは次のようになります:
import QtQuick Item { required property CppInterface interface Component.onCompleted: { interface.foo(); } }
このプロパティを required にすることで、インターフェイスプロパティが設定されないままコンポーネントが 作成されることを防ぐことができます。
createWithInitialProperties()
以外はC++ から QML オブジェクトを読み込むで説明したのと同じ方法でコンポーネントを初期化することができます:
component.createWithInitialProperties(QVariantMap{{u"interface"_s, QVariant::fromValue<CppInterface *>(new CppInterface)}});
この方法は、インターフェースをルートコンポーネントだけが使用できるようにする必要がある場合に推奨されます。また、C++側でより簡単にインターフェースのシグナルやスロットに接続することができます。
どちらの方法も必要でない場合は、代わりにC++モデルの使い方を検討するとよいでしょう。
ロードされたQMLオブジェクトにオブジェクト名でアクセスする
QML コンポーネントは基本的にオブジェクトツリーであり、その子オブジェクトは 兄弟オブジェクトやその子オブジェクトを持ちます。QML コンポーネントの子オブジェクトは、QObject::objectName プロパティとQObject::findChild() を使って探すことができます。例えば、MyItem.qml
のルート・アイテムに、Rectangle の子アイテムがあったとします:
import QtQuick Item { width: 100; height: 100 Rectangle { anchors.fill: parent objectName: "rect" } }
子オブジェクトの位置は次のようになります:
オブジェクトは同じobjectName
を持つ複数の子を持つことができることに注意してください。たとえば、ListView はそのデリゲートのインスタンスを複数作成するので、そのデリゲートが特定の objectName で宣言されている場合、ListView は同じobjectName
を持つ複数の子を持つことになります。この場合、QObject::findChildren() を使用して、一致するobjectName
を持つすべての子を見つけることができます。
警告 C++からQMLオブジェクトにアクセスし、それらを操作することは可能ですが、 テストやプロトタイピングを目的とする場合を除き、推奨される方法ではありません。QMLとC++の統合の強みの一つは、C++のロジックやデータセットのバックエンドとは別に、QMLでUIを実装できることです。このようなアプローチでは、QMLのUIをC++のUIに影響を与えずに変更することも難しくなります。
C++からQMLオブジェクト型のメンバへのアクセス
プロパティ
QML オブジェクトの中で宣言されたプロパティは、自動的に C++ からアクセスできるようになります。次のような QML アイテムがあるとします:
someNumber
プロパティの値はQQmlProperty 、あるいはQObject::setProperty() やQObject::property() を使って設定したり読み取ったりすることができます:
QQmlEngine engine; QQmlComponent component(&engine, "MyItem.qml"); QObject *object = component.create(); qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt(); QQmlProperty::write(object, "someNumber", 5000); qDebug() << "Property value:" << object->property("someNumber").toInt(); object->setProperty("someNumber", 100);
QML プロパティの値を変更する際には、必ずQObject::setProperty(),QQmlProperty (),QMetaProperty::write() を使用し、QML エンジンがプロパティの変更を認識できるようにする必要があります。例えば、m_buttonText
のメンバ変数の値を内部的に反映するbuttonText
プロパティを持つカスタム型PushButton
があるとします。このようにメンバ変数を直接変更することは良いアイデアではありません:
//bad code QQmlComponent component(engine, "MyButton.qml"); PushButton *button = qobject_cast<PushButton*>(component.create()); button->m_buttonText = "Click me";
値が直接変更されるため、Qtのメタオブジェクトシステムをバイパスすることになり、QMLエンジンはプロパティの変更を認識することができません。つまり、buttonText
へのプロパティのバインディングは更新されず、onButtonTextChanged
ハンドラも呼び出されません。
QMLメソッドの呼び出し
すべてのQMLメソッドはメタオブジェクトシステムに公開されており、C++からQMetaObject::invokeMethod() を使って呼び出すことができます。以下のコードにあるように、コロンの後にパラメータと戻り値の型を指定することが できます。これは例えば、C++のあるシグネチャを持つシグナルをQMLで定義されたメソッドに接続したい場合などに便利です。型を省略した場合、C++のシグネチャはQVariant を使用します。
以下はQMetaObject::invokeMethod() を使って QML メソッドを呼び出す C++ アプリケーションです:
QML | // MyItem.qml import QtQuick Item { function myQmlFunction(msg: string) : string { console.log("Got message:", msg) return "some return value" } } |
C++ | // main.cpp QQmlEngine engine; QQmlComponent component(&engine, "MyItem.qml"); QObject *object = component.create(); QString returnedValue; QString msg = "Hello from C++"; QMetaObject::invokeMethod(object, "myQmlFunction", Q_RETURN_ARG(QString, returnedValue), Q_ARG(QString, msg)); qDebug() << "QML function returned:" << returnedValue; delete object; |
コロンの後に指定されたパラメータと戻り値の型に注目してください。型名には値型や オブジェクト型が使えます。
もし型が省略されていたり、QMLの中でvar
と指定されている場合は、QMetaObject::invokeMethod を呼び出す際に、Q_RETURN_ARG()とQ_ARG()で型としてQVariant を渡す必要があります。
QMLシグナルへの接続
すべての QML シグナルは自動的に C++ で利用可能となり、通常の Qt C++ シグナルと同様にQObject::connect() を使って接続することができます。その代わり、C++のシグナルはシグナルハンドラを用いてQMLオブジェクトに受信させることができます。
以下は、qmlSignal
という名前のシグナルを持つ QML コンポーネントです。 は文字列型のパラメータで発信されます。このシグナルはQObject::connect() を使って C++ オブジェクトのスロットに接続され、qmlSignal
が発信されるたびにcppSlot()
メソッドが呼び出されるようになっています:
class MyClass : public QObject { Q_OBJECT public slots: void cppSlot(const QString &msg) { qDebug() << "Called the C++ slot with message:" << msg; } }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QObject *item = view.rootObject(); MyClass myClass; QObject::connect(item, SIGNAL(qmlSignal(QString)), &myClass, SLOT(cppSlot(QString))); view.show(); return app.exec(); } |
シグナルのパラメータに含まれる QML オブジェクトの型は、C++ のクラスへのポインタに変換されます:
class MyClass : public QObject { Q_OBJECT public slots: void cppSlot(QQuickItem *item) { qDebug() << "Called the C++ slot with item:" << item; qDebug() << "Item dimensions:" << item->width() << item->height(); } }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QObject *item = view.rootObject(); MyClass myClass; QObject::connect(item, SIGNAL(qmlSignal(QVariant)), &myClass, SLOT(cppSlot(QVariant))); view.show(); return app.exec(); } |
本ドキュメントに含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。