c++qtqml

QML: Type Error with custom QObject


I'm trying out something in QML to try and make it alittle easier to merge the two more seamlessly; to be precise, I'm trying to link an object with structured data to QML.

I have the following setup:

main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "dataobject.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    qmlRegisterType<DataObject>("DO", 1,0,"DataObject");

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

    return app.exec();
}

dataobject.h:

#ifndef DATAOBJECT_H
#define DATAOBJECT_H

#include <QObject>
#include <QVariant>
#include <iostream>

class DataObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(qreal a MEMBER a NOTIFY aChanged)
    Q_PROPERTY(qreal b MEMBER b NOTIFY bChanged)
public:
    explicit DataObject(QObject *parent = 0);

signals:
    void aChanged();
    void bChanged();

public slots:
    void printState() {
        using namespace std;
        cout << a << ", " << b << endl;
    }

private:
    qreal a;
    qreal b;
};

#endif // DATAOBJECT_H

dataobject.cpp:

#include "dataobject.h"

DataObject::DataObject(QObject *parent) :
    QObject(parent)
{
    connect(this, SIGNAL(aChanged()), this, SLOT(printState()));
    connect(this, SIGNAL(bChanged()), this, SLOT(printState()));
}

main.qml:

import DO 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    menuBar: MenuBar {
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit();
            }
        }
    }
    Text {
        text: qsTr("Hello World")

        MouseArea {
            property DataObject myData;
            anchors.fill: parent
            drag.target: parent

            onReleased: {
                myData.a = mouse.x;
                myData.b = mouse.y;
            }
        }
    }
}

qml.qrc:

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
    </qresource>
</RCC>

Now, what I'd hoped was that values generated by QML could be feed into a object in C++ directly (i.e. the onReleased handler in the MouseArea trying to write to the myData field). However, this basic proof of concept doesn't work, but I don't really understand why.

The error I get (on drag and release of the mouse button) is: qrc:///main.qml:29:TypeError:Type error

Which matches up with the line "myData.a = mouse.x;", so it fails straight away.

Any idea's where I'm going wrong? I've tried with the fields being int, double, QVariant, and qreal, none of which have worked. Is in a fundamental inability in QML to link objects like that? If so, any idea how, for example, anchors.fill is implemented in the Qt source code?


Solution

  • It helps if you break down the expression on the line where the error is coming from. Try just printing myData.a first:

    print(myData.a)
    myData.a = mouse.x;
    myData.b = mouse.y;
    

    qrc:///main.qml:31: TypeError: Cannot read property 'a' of null

    So, myData is null. We can verify this with another QObject-based type:

    MouseArea {
        property DataObject myData;
        property Item item
        anchors.fill: parent
        drag.target: parent
    
        onReleased: {
            print(item)
            myData.a = mouse.x;
            myData.b = mouse.y;
        }
    }
    

    qml: null

    So, you can fix this error by initialising the property:

    property DataObject myData: DataObject {}
    

    You can think of QObject-based properties as pointers for JavaScript; they can be null or point to a valid object... or be undefined. :) I can't find anything mentioned about this here, but that's where this behaviour should be mentioned.

    If you'd like to simplify things, you can have the object default-constructed for you by making it a child object of the MouseArea, rather than a property:

    MouseArea {
        DataObject {
            id: myData
        }
        anchors.fill: parent
        drag.target: parent
    
        onReleased: {
            myData.a = mouse.x;
            myData.b = mouse.y;
        }
    }
    

    Currently, you won't be able to refer to this property from C++. However, you can achieve this in two ways:

    1. Declare a property alias to the item.
    2. Give it an objectName and use QObject::findChild to find it.