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

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.

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();
}

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

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.

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()
}

Routinely change QML property value from within a threaded c++ loop

I'm brand new to c++ and QML, and am struggling to call and run a threaded loop. I'd like to add an integer to a QML propery value every x milliseconds.
I'll omit my main.qml code for now, as I'm able to access the desired property; this question pertains more to c++.
void fn(QQuickItem x)
{
for (;;)
{
std::this_thread::sleep_for(std::chrono.milliseconds(100));
x->setProperty("value", x->property("value").toReal() + 10);
}
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
//QScopedPointer<TachometerDemo> Tacho(new TachometerDemo);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QQuickItem *item = engine.rootObjects().at(0)->findChild<QQuickItem*>
("circularGauge");
thread t1(fn, item);
return app.exec();
}
Any guidance on the most efficient means for achieving the above desired functionality would be appreciated. Whilst I'm currently testing on a win32 platform, a cross-platform approach is required.
Better use Qt's event loop mechanism and SIGNAL/SLOTS. And QThreads if needed. Also if you need to update value in QML in msecconds duration don't do it over setProperty, this function call takes too long. You can update your value directly in QML using JS. But if you need to update QML value from C++ you can do it this way:
test.h
#include <QObject>
#include <QTimer>
class Test : public QObject
{
Q_OBJECT
public:
Test();
Q_PROPERTY(int value READ value NOTIFY valueChanged)
int value(){return this->m_value;}
signals:
void valueChanged();
private slots:
void timeout();
private:
int m_value;
QTimer * m_timer;
};
test.cpp
#include "test.h"
Test::Test():m_value(0)
{
this->m_timer = new QTimer(this);
this->m_timer->setInterval(100);
connect(this->m_timer, &QTimer::timeout, this, &Test::timeout);
this->m_timer->start();
}
void Test::timeout()
{
this->m_value += 10;
emit valueChanged();
}
in your main.cpp
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new Test());
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
Somewhere in your QML:
Label
{
text: Test.value
anchors.centerIn: parent
}
This is the fastest way to update QML value from C++

setMainQmlFile`, rootObject and showExpanded are not members of QQmlApplicationEngine

I've written this piece of code following some guys tutorial but I can't get it to run. The error says:
setMainQmlFile`, rootObject and showExpanded are not members of
QQmlApplicationEngine
What it's supposed to do is get a signal from QML and print out a message (in console). Basically I'm trying to integrate C++ and QML.
EDIT
I've tried to replace some of the functions with some others that seemed appropriate (at least to me). I've also tried to find what to include so that these functions would work but with no luck.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "qtquickglobal.h"
#include <QQmlContext>
#include "myclass.h"
#include <QtCore>
#include <QtDebug>
#include <QQuickWindow>
int main(int argc, char *argv[]){
//Q_OBJECT;
QGuiApplication app(argc, argv);
QQmlApplicationEngine viewer;
viewer.load(QUrl(QStringLiteral("Qt/Versuch2/main.qml")));
myclass data;
viewer.rootContext() ->setContextProperty("myclassData", &data);
viewer.setMainQmlFile(QStringLiteral("qml/Versuch2/main.qml"));
QObject *viewerobject = viewer.rootObject();
QObject::connect(viewerobject, SIGNAL(qmlSignal(QString)), &data, SLOT(cppSlot(QString)));
viewer.showExpanded();
return app.exec();
}
void myclass::cppSlot(QString msg) {
qDebug() <<QString ("Called the cpp slot with message: %1").arg(msg);
}
Thank You.
I don't know where did you find you tutorial but, regarding Qt documentation, there is no such methods as setMainQmlFile nor showExpanded for QQmlApplicationEngine.
For setMainQmlFile(...), try to use instead setSource(...).
For showExpanded(), it's a QWidget function, and QQmlApplicationEngine do not inherit QWidget.
Regarding rootObject(), it might be a typo, you can use rootObjects() which return a QList<QObject*>.
Edit: Looks like you'll have to use the Qt Quick 2 Application wizard of Qt Creator in order to re-create that QtQuick2ApplicationViewer class used in the tutorial you found.
Using Qt 5.4.0 and Qt Creator 3.3.0, create New Project:
Click New Project
Qt Quick Application
Click Choose...
Name the project and select where to place it
Click Next
Select Qt Quick 2.4 from the drop-down menu
Click Next
Select desired Kit(s)
Click Next
Click Finish
Now You should see open main.qml file with following code:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
MainForm {
anchors.fill: parent
mouseArea.onClicked: {
Qt.quit();
}
}
}
Make changes to the file so that it looks like the following:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
//### New Code ###
signal myQmlSignal(string msg)
//################
MainForm {
anchors.fill: parent
mouseArea.onClicked: {
//### New Code ###
//Replace "Qt.quit();" with
console.log("Sending myQmlSignal from QML...");
myQmlSignal("Hello from QML")
//################
}
}
}
Add new class to Your project:
Right Mouse Click project name in Projects viewer
Click Add New...
Select C++ Class if not already selected
Click Choose...
In Class name filed enter "MyCppClass"
Set Base class to QObject
Click Next
Click Finish
Open mycppclass.h file, it should look like the following:
#ifndef MYCPPCLASS_H
#define MYCPPCLASS_H
#include <QObject>
class MyCppClass : public QObject
{
Q_OBJECT
public:
explicit MyCppClass(QObject *parent = 0);
~MyCppClass();
signals:
public slots:
};
#endif // MYCPPCLASS_H
Make changes to mycppclass.h so it looks like this:
#ifndef MYCPPCLASS_H
#define MYCPPCLASS_H
#include <QObject>
//### New Code ###
#include <QDebug>
//################
class MyCppClass : public QObject
{
Q_OBJECT
public:
explicit MyCppClass(QObject *parent = 0);
~MyCppClass();
signals:
public slots:
//### New Code ###
void myCppSlot(const QString &msg);
//################
};
#endif // MYCPPCLASS_H
Open mycppclass.cpp, which should look like this:
#include "mycppclass.h"
MyCppClass::MyCppClass(QObject *parent) : QObject(parent)
{
}
MyCppClass::~MyCppClass()
{
}
Change it to this:
#include "mycppclass.h"
MyCppClass::MyCppClass(QObject *parent) : QObject(parent)
{
}
MyCppClass::~MyCppClass()
{
}
void MyCppClass::myCppSlot(const QString &msg)
{
//### New Code ###
qDebug() << "Signal was received by C++. It contains follwoing message: " << msg;
//################
}
Open main.cpp, which looks like this:
#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();
}
And make following changes:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
//### New Code ###
#include "mycppclass.h"
//################
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//### New Code ###
MyCppClass myCppClass;
QObject::connect(engine.rootObjects().takeFirst(), SIGNAL(myQmlSignal(QString)), &myCppClass, SLOT(myCppSlot(QString)));
//################
return app.exec();
}
Click big green triangle to compile and run Your application. Watch Application Output area, click Hello World, You should see following message being printed out:
qml: Sending myQmlSignal from QML...
Signal was received by C++. It contains follwoing message: "Hello from QML"