1

I am trying to write code that will pass some data from C++ engine to the Qml scripts via signal, but it looks like that I doing some thing wrong, because when I receive signal in Qml my object don't any method or properties! Look at that code:

Signaller - class that invoke signal: signaller.h:

class Signaller : public QObject
{
    Q_OBJECT
public:
    explicit Signaller(QObject *parent = 0);
    Q_INVOKABLE void invokeSignal();
signals:
    void mysignal(TestClass test);
public slots:

};

signaller.cpp:

Signaller::Signaller(QObject *parent) :
    QObject(parent)
{
}

void Signaller::invokeSignal()
{
    TestClass s;
    emit mysignal(s);
}

TestClass - class that will be passed to Qml engine, and which must have test method in Qml script:

Test.h:

class TestClass : public QObject
{
    Q_OBJECT
public:
    explicit TestClass(QObject *parent = 0);
             TestClass(const TestClass& obj);
             ~TestClass();
     Q_INVOKABLE void test();

signals:

public slots:

};

Q_DECLARE_METATYPE(TestClass)

Test.cpp:

TestClass::TestClass(QObject *parent) :
    QObject(parent)
{
    qDebug()<<"TestClass::TestClass()";
}

TestClass::TestClass(const TestClass &obj) :
    QObject(obj.parent())
{
    qDebug()<<"TestClass::TestClass(TestClass &obj)";
}


TestClass::~TestClass()
{
    qDebug()<<"TestClass::~TestClass()";
}

void TestClass::test()
{
    qDebug()<<"TestClass::test";
}

Those 2 classes also registered in main function:

int main(int argc, char *argv[])
{
    // SailfishApp::main() will display "qml/template.qml", if you need more
    // control over initialization, you can use:
    //
    //   - SailfishApp::application(int, char *[]) to get the QGuiApplication *
    //   - SailfishApp::createView() to get a new QQuickView * instance
    //   - SailfishApp::pathTo(QString) to get a QUrl to a resource file
    //
    // To display the view, call "show()" (will show fullscreen on device).

    qmlRegisterType<TestClass>("Test", 1, 0, "Test");
    qmlRegisterType<Signaller>("Signaller", 1, 0, "Signaller");

    return SailfishApp::main(argc, argv);
}

That is my Qml file with test code:

import Signaller 1.0

Page {

    Signaller {
        id: sig
        Component.onCompleted: sig.invokeSignal()
        onMysignal: {
            console.log("signal",typeof test);
            console.log(typeof test.test);
        }
    }
}

And the log:

[D] TestClass::TestClass:6 - TestClass::TestClass() 
[D] TestClass::TestClass:12 - TestClass::TestClass(TestClass &obj) 
[D] TestClass::TestClass:12 - TestClass::TestClass(TestClass &obj) 
[D] onMysignal:41 - signal object
[D] onMysignal:42 - undefined
[D] TestClass::~TestClass:18 - TestClass::~TestClass() 
[D] TestClass::~TestClass:18 - TestClass::~TestClass() 

As you can see from log, TestClass.test field is empty after passing to the Qml. What I am doing wrong?

3 Answers 3

6

You are passing a QObject derived object by value through the signal/slot system - you should never do this (docs). Create it on the heap and send it's pointer through.

In this case you'll probably want it's lifetime controlled via QML, you can set that by calling QQmlEngine::setObjectOwnership( s, QQmlEngine::JavaScriptOwnership).

Sign up to request clarification or add additional context in comments.

4 Comments

That page is the Holy Grail: what a perfect synthesis. :)
Thanks, that works, however I don't understand how Qml garbage collector works, because now I create element in heap, set object ownership to JavaScriptOwnership and emit signal with that object. In Qml I have only log function call, so there is no more pointers to that object, however I don't see removal of my object (destructor is not called). Will it be called later, when memory will be full (like in Java), or Qml delete object right after no one pointed on the object (in this case I again have an error)?
From the docs (doc.qt.io/qt-5/…): "The engine will periodically collect all unreferenced data on the JavaScript heap". The key word here is "periodically", you have no guarantee when it's going to happen (unless you manually invoke the collector).
I don't understand why you think there would be an error if " Qml delete object right after no one pointed on the object" in this circumstance as the object is an argument to the signal, so there would be a reference to it for at least the whole signal handler block.
3

QObject should not be used by value in signals and slots. Also, It is a bad idea to implement a copy constructor for a QObject subclass when QObject itself hides its copy constructor.

Thus change your signals to pass pointers to QObject and It will be fine. There is a short and good reference on how to communicate between C++ and Qml

ps: You don't need to register classes which declare the Q_OBJECT macro.

1 Comment

Good comment on the copy constructor, that's very bad practise.
3

This is an example I made for myself for testing C++ <--> QML interaction:

//File: animal.h
#ifndef ANIMAL_H
#define ANIMAL_H

#include <QObject>

class Animal : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString animal_name READ get_animal_name WRITE set_animal_name)
public:
    explicit Animal(QObject *parent = 0);
    QString get_animal_name();
    void set_animal_name(QString p_name);
private:
    QString     animal_name;

};

#endif // ANIMAL_H

//File: animal.cpp
#include "animal.h"

Animal::Animal(QObject *parent) : QObject(parent)
{
}
void Animal::set_animal_name(QString p_name) {
    animal_name=p_name;
}
QString Animal::get_animal_name() {
    return animal_name;
}

//File: zoo.h
#ifndef ZOO_H
#define ZOO_H

#include <QObject>

class Animal;
class Zoo : public QObject
{
    Q_OBJECT
public:
    explicit Zoo(QObject *parent = 0);
    Q_INVOKABLE Animal* get_animal_by_index(int index);
    Q_INVOKABLE void add_animal(Animal *a);
    Q_INVOKABLE void dump_animal_info(Animal *a);
    Q_INVOKABLE void emit_signal();
signals:
    void some_signal(Animal *animal_object);
public slots:

private:
       QList<Animal*>       animals;
};

#endif // ZOO_H


//File: zoo.cpp
#include <QDebug>
#include "zoo.h"
#include "animal.h"

Zoo::Zoo(QObject *parent) : QObject(parent)
{
    Animal *a;

    a=new Animal();
    a->set_animal_name("Black Bear");
    add_animal(a);

    a=new Animal();
    a->set_animal_name("Gray Wolf");
    add_animal(a);
}
Animal* Zoo::get_animal_by_index(int index) {
    return animals.at(index);
}
void Zoo::add_animal(Animal *a) {
    animals.append(a);
}
void Zoo::dump_animal_info(Animal *a) {
    qWarning() << "animal_name=" << a->get_animal_name();
}
void Zoo::emit_signal() {
    Animal *a;
    a=animals.at(0);
    emit some_signal(a);
}

//File: main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "animal.h"
#include "zoo.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    qmlRegisterType<Zoo>("zoo",1,0,"Zoo");
    qmlRegisterType<Animal>("zoo.animal",1,0,"Animal");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

//File: main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0

import zoo 1.0
import zoo.animal 1.0

ApplicationWindow {
    visible: true
    width: 640; height: 480; title: qsTr("Zoo")

    Zoo {
        id: zoopark
    }
    Column {
        Button {
            text: "Test C++ <--> QML data exchange by Method"
            onClicked: {
                var animal=zoopark.get_animal_by_index(1);
                zoopark.dump_animal_info(animal);
            }
        }
        Button {
            text: "Text C++ <--> QML data exchage by Signal"
            onClicked: {
                zoopark.emit_signal();
            }
        }
    }
    Connections {
        target: zoopark
        onSome_signal: {
            console.log('signal received');
            console.log('from qml: animal name=' + animal_object.animal_name)
            console.log('dumping animal info from c++:')
            zoopark.dump_animal_info(animal_object)
        }
    }

    function process_signal() {
        console.log('from qml animal name=' + animal_object.animal_name)
        zoopark.dump_animal_info(animal_object)
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.