[2016/08/17 19:51 - 少し手直し]
Qtをさわり始めてQMLとC++のバインディングで引っかかったのでまとめてみました。間違いやもっと良い方法があればご指摘下さい。
以下では、QtCreatorでQtQuickアプリケーションのプロジェクトを作成した際に自動で生成される下記のソースコードをベースにします。(ここではui.qmlは使用しません)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
#C++からQMLオブジェクトへアクセス
いろいろな方法があるようですが、ここではobjectNameでアクセスする方法を取り上げます。
main.cppではroot要素へのアクセスを経て、子要素であるTextへQML側で定義するobjectNameでアクセスする。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/* added */
//(1) Get an access to root object and qml object by objectName "textObject" defined in main.qml
QObject *rootObject = engine.rootObjects().first();
QObject *qmlObject = rootObject->findChild<QObject*>("textObject");
// (2) set an object property
qmlObject->setProperty("text", "Text from C++");
//----------
return app.exec();
}
QML側ではアクセスしたい子要素にobjectNameを定義する。
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
/*added*/
objectName: ”textObject”
// ----------
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
結果
(見にくかったので色をつけてフォントを大きくしています)
なお、オフィシャルではC++からQMLオブジェクトへのアクセスはC++とQMLの独立性の観点から推奨していないようです。
Warning: While it is possible to use C++ to access and manipulate QML objects deep into the object tree, we recommend that you do not take this approach outside of application testing and prototyping. One strength of QML and C++ integration is the ability to implement the QML user interface separately from the C++ logic and dataset backend, and this strategy breaks if the C++ side reaches deep into the QML components to manipulate them directly. This would make it difficult to, for example, swap a QML view component for another view, if the new component was missing a required
objectName
. It is better for the C++ implementation to know as little as possible about the QML user interface implementation and the composition of the QML object tree.
#QMLからC++のクラスへアクセス
ContextPropertyにQObjectインスタンスを埋め込んで、QMLからメソッドをコールする例を挙げてみます。(オフィシャルドキュメントではQDateTimeを返すようになっていましたがもっとシンプルにstringを返す形にしてみました)
まずヘッダファイルの準備。
#ifndef APPLICATIONDATA_H
#define APPLICATIONDATA_H
#include <QObject>
#include <QString>
class ApplicationData:public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QString getTextFromCpp(){
return QString("This is the text from C++");
}
};
#endif // APPLICATIONDATA_H
続いて、cppファイルとqmlファイルを変更。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
/*added*/
#include <QQmlContext>
#include "applicationdata.h"
//----------
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/*added*/
ApplicationData data;
engine.rootContext()->setContextProperty("applicationData",&data);
//----------
return app.exec();
}
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
/*changed*/
//text: qsTr("Hello World")
text: applicationData.getTextFromCpp()
//----------
anchors.centerIn: parent
}
}
Signal & Slot
今回は、QMLのボタンクリックでC++側へシグナルを出し、C++側のスロットで受け取ったら、逆にC++側からQML側へシグナルを送るというのを例にします。
C++でConnect
まずは、C++側でConnectする例。cppからのシグナルでQML側のSlotが認識されない、No such slotというエラーで泣かされました。引数をQVariantにするとうまくいく。
import QtQuick 2.5
import QtQuick.Window 2.2
/*added*/
import QtQuick.Controls 2.0
//----------
Window {
/*added*/
signal qmlSignal(string msg);
function qmlSlot(text)
{
console.log("qmlSlot is called with the text: " + text)
textField.text = text;
}
//----------
visible: true
width: 640
height: 480
title: qsTr("Hello World")
/*deleted*/
// MouseArea {
// anchors.fill: parent
// onClicked: {
// Qt.quit();
// }
// }
Text {
/*added*/
id:textField
//----------
text: qsTr("Hello World")
anchors.centerIn: parent
}
/*added*/
Button{
id:aButton
text:"Emit Signal!"
anchors.centerIn: parent
anchors.verticalCenterOffset: 30
onClicked: qmlSignal("Hello from QML")
}
//----------
}
#ifndef CPPSIGNALSLOT_H
#define CPPSIGNALSLOT_H
#include <QObject>
#include <QVariant>
class CppSignalSlot : public QObject
{
Q_OBJECT
public:
explicit CppSignalSlot(QObject *parent = 0);
signals:
void cppSignal(QVariant text);
public slots:
void cppSlot(QString msg);
};
#endif // CPPSIGNALSLOT_H
#include "cppsignalslot.h"
#include <QDebug>
CppSignalSlot::CppSignalSlot(QObject *parent) : QObject(parent)
{
}
void CppSignalSlot::cppSlot(QString msg)
{
qDebug() << "cppSlot is called with the message: " << msg;
emit cppSignal("Hello from C++");
}
#include <QGuiApplication>
#include <QQmlApplicationEngine>
/*added*/
#include <QQmlContext>
#include "cppsignalslot.h"
//----------
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
CppSignalSlot obj;
QObject *root = engine.rootObjects().first();
// Connect QML Signal to C++ Slot
QObject::connect(root, SIGNAL(qmlSignal(QString)),
&obj, SLOT(cppSlot(QString)));
// Connect C++ Signal to QML Slot
QObject::connect(&obj, SIGNAL(cppSignal(QVariant)),
root, SLOT(qmlSlot(QVariant)));
return app.exec();
}
クリック後
(上のセクションとwindowが違うのは試験環境が違うためです。。。それ以上の理由はありません)
[コンソール出力]
cppSlot is called with the message: "Hello from QML"
qml: qmlSlot is called with the text: Hello from C++
QMLでConnect
次は同じ例でQML側でConnectする例です。後述の通り、Cannot assign to non-existent property とReferenceErrorに泣かされました。
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
/*deleted*/
// MouseArea {
// anchors.fill: parent
// onClicked: {
// Qt.quit();
// }
// }
Text {
id: textField
text: qsTr("Hello World")
anchors.centerIn: parent
}
Button{
text:"Emit Signal!"
anchors.centerIn: parent
anchors.verticalCenterOffset: 30
onClicked: cppSignalSlot.cppSlot("Hello from QML")
}
Connections
{
target:cppSignalSlot
onCppSignal:{
console.log("received cppSignal:" + text)
textField.text = text;
}
}
}
#ifndef CPPSIGNALSLOT_H
#define CPPSIGNALSLOT_H
#include <QObject>
class CppSignalSlot : public QObject
{
Q_OBJECT
public:
explicit CppSignalSlot(QObject *parent = 0);
signals:
void cppSignal(QString text);
public slots:
void cppSlot(QString text);
};
#endif // SIGNALSLOT_H
#include "cppsignalslot.h"
#include <QDebug>
CppSignalSlot::CppSignalSlot(QObject *parent) : QObject(parent)
{
}
void CppSignalSlot::cppSlot(QString text)
{
qDebug() << "cppSlot is called with text: " + text;
emit cppSignal(QString("Hello from C++"));
}
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "cppsignalslot.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
SignalSlot obj;
// Put this before lading qml file!
engine.rootContext()->setContextProperty("cppSignalSlot",&obj);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
ここでミソなのは、C++からcppSignal()でシグナルを出した時の動作をonCppSignalと書くことです。(要するにC++側はLower Camelで書いて、QML側はon+Upper Camelで書きましょうということみたいです)
あと、コメントにも書きましたが、main.cpp
でRoot ObjectのContextに設定する前にmain.qml
をロードすると、下記のエラーが出ます。(でも、動作はする。。。)
QML Connections: Cannot assign to non-existent property "OnCppSignal"
ReferenceError: cppSignalSlot is not defined
#参考
- Integrating QML and C++
- Qt Quick での C++ × QML バインディングについてまとめてみた
- How can I access my Window object properties from C++ while using QQmlApplicationEngine?
- How to connect a QML gui with a c++ application
- QML2 to C++ and back again, with signals and slots
- Exchange Data and Objects between C++ and QML and vice versa
- Not able to connect c++ signal to qml slot using QML connections
そして、結局Qtのオフィシャルドキュメントが一番わかりやすいというオチ。。。