1

My goal is to pass an instance of the custom C++ class “Contact” as a parameter “contact” of a signal “contactFound” emitted from the C++ class “ContactHelper” to QML; and in QML in the “onContactFound” signal handler to access the properties of the signal parameter “contact”.

From the QML side, the object “contact” is basically a data structure, with 2 read-only properties, and no methods.

I have tried 2 approaches, both shown in the example code at the bottom of this question. EDIT: In the original version of this question, neither worked. Thanks to comments from @BaCaRoZzo, approach 1 now works (and thus solves my immediate problem).

Approach 1) “Contact” is modelled on the Custom Type examples “Block” and “Message” in the QT documentation. i.e. “Contact” is declared via qRegisterMetaType, and is NOT based on QObject, but uses the Q_GADGET macro (following input from BaCaRoZzo).

This approach works both when contact is passed to the signal by reference (&contact), and by value (contact), but fails when passed by by pointer / address (*contact).

Approach 2) “Contact2” based on a QObject, declared via qmlRegisterType, and passed by pointer / address (*contact2).

The code compiles and runs, but neither approach gave me the expected signal parameter property access.

Approach 1) in QML the parameter “contact” of “onContactFound” is valid, but the properties of contact are undefined. EDIT: this is fixed by adding the missing Q_GADGET macro to contact.h

Approach 2) in QML the parameter “contact” of “onContactFound2” is not defined!

EDIT: Given that Approach 1 now works, I can use it in my real code, and slightly change the focus of this question, namely:

a) What is stopping Approach 2 (Object / qmlRegisterType / passed by pointer *) from working?

b) Assuming both approaches can be got working, which is the most suitable for transferring a light-weight read-only data-structure (a handful of string properties + 1 string array property) via signal parameter from C++ to QML?

I am developing with Qt5.5.1, with an Android 6.0.1 device as the target.

The example code below can be pasted into a new “QtQuick Controls Application” project.

main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "contactshelper.h"
#include "contact.h"
#include "contact2.h"
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    qmlRegisterType<ContactsHelper>("ContactsHelper",1,0,"ContactsHelper");
    qRegisterMetaType<Contact>();
    qmlRegisterType<Contact2>("ContactsHelper",1,0,"Contact");
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

contact.h

#ifndef CONTACT_H
#define CONTACT_H
#include <QDebug>
#include <QMetaType>
class Contact {
    Q_GADGET //!!!This macro was missing in the original question!!!
public:
    Contact();
    Contact(const Contact &other);
    ~Contact();
    Q_PROPERTY(QString contactId MEMBER m_contactId)
    Q_PROPERTY(QString displayLabel MEMBER m_displayLabel)
    void setContactId(const QString contactId);
    void setDisplayLabel(const QString displayLabel);
private:
    QString m_contactId;
    QString m_displayLabel;
};
Q_DECLARE_METATYPE(Contact)
#endif // CONTACT_H

contact.cpp

#include "contact.h"
#include <QDebug>
Contact::Contact(){}
Contact::~Contact(){}
Contact::Contact(const Contact &other) {
    qDebug() << "Contact: copy constructor called: " << other.m_displayLabel;
    m_contactId = other.m_contactId;
    m_displayLabel = other.m_displayLabel;
}
void Contact::setContactId(const QString contactId) {
    m_contactId = contactId;
}
void Contact::setDisplayLabel(const QString displayLabel) {
    m_displayLabel = displayLabel;
}

contact2.h

#ifndef CONTACT2_H
#define CONTACT2_H
#include <QDebug>
#include <QObject>
class Contact2: public QObject
{
    Q_OBJECT
public:
    explicit Contact2(QObject *parent = 0);
    ~Contact2();
    Q_PROPERTY(QString contactId MEMBER m_contactId)
    Q_PROPERTY(QString displayLabel MEMBER m_displayLabel)
    void setContactId(const QString contactId);
    void setDisplayLabel(const QString displayLabel);
private:
    QString m_contactId;
    QString m_displayLabel;
};
#endif // CONTACT2_H

contact2.cpp

#include "contact2.h"
Contact2::Contact2(QObject *parent) : QObject(parent) {}
Contact2::~Contact2() {}
void Contact2::setContactId(const QString contactId) {
    m_contactId = contactId;
}
void Contact2::setDisplayLabel(const QString displayLabel) {
    m_displayLabel = displayLabel;
}

contactshelper.h

#ifndef CONTACTSHELPER_H
#define CONTACTSHELPER_H
#include "contact.h"
#include "contact2.h"
#include <QObject>
class ContactsHelper : public QObject {
    Q_OBJECT
public:
    explicit ContactsHelper(QObject *parent = 0);
    Q_INVOKABLE void fakeContactFound();
    Q_INVOKABLE void fakeContactFound2();
signals:
    void contactFound(const int index, const int count, const Contact &contact);
    void contactFound2(const int index, const int count, const Contact *contact);
};
#endif // CONTACTSHELPER_H

contactshelper.cpp

#include "contactshelper.h"
#include "contact.h"
ContactsHelper::ContactsHelper(QObject *parent) : QObject(parent) {}
void ContactsHelper::fakeContactFound() {
    qDebug() << "ContactsHelper: faking contactFound";
    Contact contact;
    contact.setContactId("123");
    contact.setDisplayLabel("Aunt Agatha");
    contactFound(1, 1, contact);
}
void ContactsHelper::fakeContactFound2() {
    qDebug() << "ContactsHelper: faking contactFound";
    Contact2 *contact  = new Contact2;
    contact->setContactId("123");
    contact->setDisplayLabel("Uncle Fred");
    this->contactFound2(1, 1, contact);
}

main.qml

import QtQuick 2.5
import QtQuick.Controls 1.4
import ContactsHelper 1.0
ApplicationWindow {
    visible: true
    ContactsHelper {
        id: contactsHelper
        onContactFound: {
            console.log ("QML: onContactFound signal received!");
            console.log ("QML: displayLabel: " + contact.displayLabel);
            listProperty(contact);
        }
        onContactFound2: {
            console.log ("QML: onContactFound2 signal received!");
            console.log ("QML: displayLabel: " + contact.displayLabel);
            listProperty(contact);
        }
        function listProperty(item) {
            console.log ("QML: listing contact properties ...");
            for (var p in item)
                console.log(p + ": " + item[p]);
        }
    }
    MainForm {
        anchors.fill: parent
        button1.onClicked: {contactsHelper.fakeContactFound()}
        button2.onClicked: {contactsHelper.fakeContactFound2()}
    }
}

MainForm.ui.qml

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
    width: 640
    height: 480
    property alias button1: button1
    property alias button2: button2
    RowLayout {
        anchors.centerIn: parent
        Button {
            id: button1
            text: qsTr("Fake contactFound")
        }
        Button {
            id: button2
            text: qsTr("Fake contactFound2")
        }
    }
}
FlyingSheep
  • 804
  • 1
  • 9
  • 20
  • The code in [this example](http://stackoverflow.com/questions/31433260/passing-q-gadget-as-signal-parameter-from-c-to-qml) works fine, expect that it uses `Q_GADGET` in place of `Q_OBJECT`, which seems fine for your case. – BaCaRoZzo Dec 13 '15 at 20:24
  • HI BaCaRoZzo. I saw that, and your answer too it. Am now re-reading. – FlyingSheep Dec 13 '15 at 21:09
  • As far as I can make out, his "Record" is equivalent to my approach 1 **Contact**, except that a) he uses the` Q_GADGET` macro, and b) in his **ListenP** (equivalent to my **ContactsHelper**) the **gotRecord** Signal takes the **Record** parameter _by value_ (as opposed to my attempts by _reference_ / _pointer_) – FlyingSheep Dec 13 '15 at 21:17
  • Approach 3 (Q_GADGET macro + object _pass by value_ to Signal parameter) works! 1000Grazie! I am still intrigued to find out what is wrong with approaches 1 and 2. I will update the question tomorrow morning with the new 3rd approach (must go to bed now). Also the test function listProperty() that I picked up [here](http://stackoverflow.com/questions/20293838/qml-list-all-object-members-properties-in-console) does not work ... – FlyingSheep Dec 13 '15 at 22:08
  • I'm glad you solved. Well actually it should work also with the `Q_OBJECT`. It's quite strange it does not. The `Q_GADGET` was an addition of Qt 5.5 to use value types in QML without the overhead of the full QObject-related stuff. It's not mandatory. Can't speak for your code in detail since I just skimmed it when I've post my first comment. – BaCaRoZzo Dec 15 '15 at 17:02
  • ciao @BaCaRoZzo. I guess I have made a silly mistake with Q_OBJECT, that stops it working, and we will groan when we spot it. But given that in my use-case I am passing a read-only data structure to QML, Q_GADGET is a better fit anyway. [My current, work-in-progress code is here](https://github.com/FlyingSheepOnSailfish/Landed4Android). Getting a Q_GADGET as a child of a Q_GADGET working was also tough, I had to workout how to override C++ operators. Ironically, I found it easier to pass data from Android / Java to C++, then to pass the same data further within Qt from C++ to QML! – FlyingSheep Dec 16 '15 at 19:44
  • Well, yeah, passing data between Java and C++ can be considered easier. It all boils down to have a nice, well explained example. :) – BaCaRoZzo Dec 16 '15 at 20:44

0 Answers0