QML instantiates C++ objects. How do I access their methods? - c++

Here's what my main.cpp looks like:
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QCoreApplication::addLibraryPath("./");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:/myqml.qml"));
view.show();
return app.exec();
}
As you can see, it creates things from myqml. Well, myqml instantiates a C++ class MyClass.
How do I access this C++ methods from the object QQuickView view? For example, I'd like to do something of the type view.MyClassInstance.myMethod1()

You want to obtain an object created in QML by C++, here it does not matter if the target has a prototype created in C ++ or not. If you want this you must obtain the object through findChild since all the objects created in QML have a kinship relationship with the window.
main.cpp
#include <QtQuick>
#include <QtGui>
class MyClass: public QObject
{
Q_OBJECT
public:
using QObject::QObject;
Q_INVOKABLE void invokable(){
qDebug()<< "invokable";
}
Q_SLOT void slot(){
qDebug()<< "slot";
}
void function(){
qDebug()<< "function";
}
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<MyClass>("foo", 1, 0, "MyClass");
QGuiApplication app(argc, argv);
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
view.show();
if(MyClass* myclass_instance = view.findChild<MyClass *>("myclass_instance")){
myclass_instance->invokable();
myclass_instance->slot();
myclass_instance->function();
}
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import foo 1.0
Rectangle {
color: "salmon"
width: 640
height: 480
MyClass{
objectName: "myclass_instance"
}
}
But this method has several drawbacks such as who manages the life cycle of the object is QML, not C ++ so the pointer could at some point point to an unreserved address. Another disadvantage is that there is a dependency of C++ to QML since if the objectName is changed in QML the code in C ++ would have to be changed.
Another approach is to create a helper class that is exported to QML with setContextProperty() and that interacts with the MyClass object.
main.cpp
#include <QtQuick>
#include <QtGui>
class MyClass: public QObject
{
Q_OBJECT
public:
using QObject::QObject;
Q_INVOKABLE void invokable(){
qDebug()<< "invokable";
}
Q_SLOT void slot(){
qDebug()<< "slot";
}
void function(){
qDebug()<< "function";
}
};
class Helper: public QObject
{
Q_OBJECT
public:
using QObject::QObject;
void call_function(){
emit called();
}
Q_SIGNAL void called();
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<MyClass>("foo", 1, 0, "MyClass");
QGuiApplication app(argc, argv);
Helper helper;
QQuickView view;
view.rootContext()->setContextProperty("helper", &helper);
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
view.show();
helper.call_function();
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import foo 1.0
Rectangle {
color: "salmon"
width: 640
height: 480
MyClass{
id: myclass
}
Connections{
target: helper
onCalled:{
myclass.invokable()
myclass.slot()
}
}
}
The advantage of this method is that there is no dependence between the logic of C++ and QML, besides the life cycle does not generate problems since the myclass objects are not handled directly in QML. The disadvantage is that you write a little more code and you can only call the Q_INVOKABLE or Q_SLOT.

Related

QML Reference Error - <thing> is not defined during a c++/QML integration

I am trying to build a mixed c++/QML application but I came across a problem when trying to make both parts communicate and interact.
The objective is to use embedded C++ object in QML via the setContextProperties method, using QQmlApplicationEngine.
I've been looking at this post QT 5.7 QML - Reference Error: Class is not defined since the problem is quite similar, but unfortunately the solution doesn't apply here. I'm still new to Qt so maybe the solution is obvious but I couldn't figure it out.
So I have 3 files, main.cpp, thing.h and main.qml.
main.cpp:
#include "thing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
Thing thing;
engine.rootContext()->setContextProperty("thing", &thing);
thing.setColor(Qt::green);
return app.exec();
}
which calls thing.h:
class Thing : public QObject
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
Thing() : _color(Qt::black), _text("text") { }
Q_INVOKABLE void clicked() { setColor(Qt::blue); }
QColor color() const {return _color;}
void setColor(const QColor &color) {
_color = color;
emit colorChanged();
}
signals:
void colorChanged();
private:
QColor _color;
};
and main.qml:
Window {id: main
width: 100; height: 100
color: thing.color
MouseArea {
anchors.fill: parent
onClicked: thing.clicked();
}
}
When running this code, I get 'qrc:/main.qml:6: ReferenceError: thing is not defined' which refers to the execution color: thing.color in main.qml. How can I get it work ?
You can try to expose your root context property "thing" before loading your main component. It will ensure that your "thing" property will be available once the component instance is created and its bindings are evaluated for the first time.
#include "thing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Thing thing;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("thing", &thing);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
thing.setColor(Qt::green);
return app.exec();
}

Fail To Connect Qml signal to C++ Slot

I have been trying to connect signal between Qml file and c++, but public slot in c++ doesn't seem to receive the signal.
What might be wrong with my program?
main.qml
Item{
id:item
signal qml_signal
Button{
onClicked: {
item.qml_signal();
}
}
}
main.cpp
QQuickView view(QUrl("qrc:/main.qml"));
QObject *item = view.rootObject();
Myclass myclass;
QObject::connect(item, SIGNAL(qml_signal()), &myclass,SLOT(cppSlot()));
myclass.h
void cppSlot() ;
myclass.cpp
void Myclass::cppSlot(){
qDebug() << "Called the C++ slot with message:";
}
When you want objects to interact between C++ and QML, you must do it on the QML side, since obtaining a QML object from C++ can cause you many problems, as in this case, the signal created in QML can not be handled in C++.
The solution is to export your object myclass to QML and make the connection there:
main.cpp
#include "myclass.h"
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQuickView view(QUrl("qrc:/main.qml"));
Myclass myclass;
view.rootContext()->setContextProperty("myclass", &myclass);
view.show();
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Controls 1.4
Item{
id:item
signal qml_signal
Button{
onClicked: item.qml_signal()
}
onQml_signal: myclass.cppSlot()
}

How can I connect qml objects, signals with mechanics and slots in c++?

I want to write mechanics in c++ and objects, signals from qml, how can I connect my main.qml with main.cpp ?
The best option will be (if it is possible) declaration, adding something like directory which make qml and c++ as one database without everytime slot and signal reference between two files
From the documentation, here's one way to connect QML objects to C++:
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(string msg)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}
}
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();
}

C++ using signal slots for QML

I have a small class that is not working properly, and I can't get what is wrong with it. The compiler gives the message:
main.cpp: error: undefined reference to 'CDetails::CDetails()'
This is the snapshot from the code:
//main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQmlContext>
#include <QDebug>
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
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);
QtQuick2ApplicationViewer viewer;
viewer.setMainQmlFile(QStringLiteral("qml/testqml/main.qml"));
viewer.showExpanded();
CDetails *test = new CDetails();
QObject::connect((QObject*)viewer.rootObject(),
SIGNAL(qmlSignal(QString)),test,
SLOT(cppSlot(QString)));
return app.exec();
}
And in main.qml:
import QtQuick 2.0
Rectangle {
id: guide
width: 360
height: 360
signal qmlSignal(string msg)
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
property double scaleFactor: 1.0
property string iconUrl: "image.png"
MouseArea {
anchors.fill: parent
onClicked: {
guide.qmlSignal("Hello from QML")
}
}
}
Update: Thanks for the suggestion on constructor. Now the error is:
error: undefined reference to 'vtable for CDetails'
What is missed here? All suggestions are welcome.
error: undefined reference to 'vtable for CDetails'
What is missed here? All suggestions are welcome.
Seems you are missing the moc include before the main function.
main.cpp
#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QDebug>
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
public slots:
void cppSlot(const QString &msg)
{ qDebug() << "Called the C++ slot with message:" << msg;
}
};
#include "main.moc"
int main(int argc, char *argv[])
{ QGuiApplication app(argc, argv);
QQuickView view;
viewer.setMainQmlFile(QStringLiteral("qml/testqml/main.qml"));
viewer.showExpanded();
CDetails *test = new CDetails();
QObject::connect((QObject*)viewer.rootObject(),
SIGNAL(qmlSignal(QString)),test,
SLOT(cppSlot(QString)));
return app.exec();
}
main.pro
...
TEMPLATE = app
TARGET = main
QT += quick
SOURCES += main.cpp
...
Note that, you will also need to add your custom lines that were there before, like dealing with the application viewer, et al.
Alternatively, you could also decouple the class and the main.cpp which means you would put the declaration of the class into a separate header, and then the defintition into a separate source file.
The main.cpp would include the freshly established header, and you would need to make sure that the new header and source file are added to the HEADERS an SOURCES variables in the qmake project file, respectively to get through the moc processing.
You're missing implementations of your constructor and destructor. Quick fix:
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
...
};

Using QQmlContext::setContextObject to make a C++ object visible to QML

Edit: Problem solved. See my edit below
I am having trouble using QQmlContext::setContextObject to make a C++ object visible to QML. I have read the documentation for QQmlContext at link, which suggests that I can use setContextObject to make the Q_PROPERTY's of a QObject-derived class visible to QML. The following code illustrates the problem.
main.cpp
#include <QObject>
#include <QQmlEngine>
#include <QGuiApplication>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myProperty READ prop NOTIFY propChanged)
public:
MyClass(QObject * parent = 0) : QObject(parent) {}
QString prop() { return QString("Hello from MyClass"); }
Q_SIGNALS:
void propChanged(void);
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlEngine engine;
QQmlContext *objectContext = new QQmlContext(engine.rootContext());
MyClass myClass;
objectContext->setContextObject(&myClass);
QQmlComponent component(&engine, "main.qml");
QObject *object = component.create(objectContext);
return app.exec();
}
main.qml
import QtQuick 2.1
import QtQuick.Controls 1.0
ApplicationWindow
{
Text
{
text: myProperty
}
}
When I run this program I get the error
file:///C:/Path/to/main.qml:8: ReferenceError: myProperty is not defined
Thank you in advance for any help.
Environment. I am using Qt 5.1.1 on Windows 7, with MSVC2010 compiler
Edit. Answering my own question. A clean rebuild showed that my build output folder clearly had some out-of-date objects in it.
One point of note: MyClass has to be in a separate file, or else the moc compiler cannot do its magic.
My tidied-up main.cpp now looks like this
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlEngine engine;
QQmlContext * context = new QQmlContext(engine.rootContext());
QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit ()));
MyClass myClass;
context->setContextObject(&myClass);
QQmlComponent component(&engine, "main.qml");
QQuickWindow * topLevel = qobject_cast<QQuickWindow*>(component.create(context));
topLevel->show();
int rc = app.exec();
delete topLevel;
delete context;
return rc;
}
You can try to add Q_INVOKABLE macro in you getter function decalration. If it will not help you can consider using QQmlContext::setContextProperty to do this. I have never seen that somebody is doing this kind of integration using ::setContextObject.