QQuickItem instantiation from C++ and setup conundrum (not allowed to pass anything to the constructor)

StackOverflow https://stackoverflow.com/questions/20962750

Question

I find myself in a pickle trying to dynamically instantiate custom QML elements from C++.

So, to layout the problem:

  • I have a light-weight Node object data structure that is abstracted from any UI.
  • Because of the memory heavy QObject and derived, I must instantiate the UI elements on demand.
  • Each Node has a _ui* member, and each UI has a _node* member.
  • Each Node has a unique _id, each UI has an ID PROPERTY, delivered from the _node*. The ID property is read only, since the ID is set upon Node instantiation and should not be modifiable.
  • QML elements must only have constructors with default parameters, e.g. cannot pass anything to the constructor.
  • When the QML element is created, the _node* member is NULL, so any QML child element of UI trying to access the ID property cannot, because the _node* is not set upon instantiation, it can only be set by an aux method, but since the property is effectively a read-only constant, it is not updated anymore after the instantiation when the _node* member of the UI is actually set.

So basically, I need to be able to set the link to the corresponding Node of each UI upon creation, so that it is accessible to the UI QML elements, but I cannot pass it to a constructor, and since the property is read-only it is read only once, upon the UI instantiation, when the _node* UI member is still NULL, so the ID value cannot be accessed.

One quick and dirty solution that comes to mind is to add a NOTIFY idChanged() signal to emit when setting the _node* member post-instantiation even though the ID property never really changes, cannot and should not, and add a check for the ID getter - return a fake dummy arbitrary value if _node* is NULL, otherwise fetch the ID value from the _node* member. Needless to say, this is not elegant, and adds some overhead and the dummy ID value is a potential can of worms, so any ideas how to defeat the ill design of QML internals are welcome.

Was it helpful?

Solution

I just came with a solution, and a rather simple one. Considering that QtQuick is designed in a way that does not allow to specify constructor parameters and considering that the "create and set" approach has huge implications in my particular usage scenario, I decided to simply make the value available to/in the UI constructor even if not passed there as a parameter.

So, instead of "create and set" I just do a "set and create" and use a static class member that is set before creating each item and used in the item constructor to set the data in time.

class UI : public QQuickItem
{
    Q_OBJECT
public:
    explicit UI(QQuickItem * parent = 0);
    ...
private:
    Object * object;
    static UI * rootUI;
};

Object * UI::protoObject = 0;

UI::UI(QQuickItem * parent) : QQuickItem(parent), object(protoObject) {
    if (!protoObject) qDebug() << "ERROR: prototype object is 0, unexpected";
    ...
}

and the actual object creation:

UI::setProtoObject(obj);
// create QQmlComponent with obj set being set in the constructor
UI::setProtoObject(0);

Also, another method, which is applicable when you don't need the object set as early as in the constructor is to create the QQmlComponents beginCreate(), set the needed properties, and finalize with completeCreate()

OTHER TIPS

Since QML components are always genetic structure with no specific data, you can not avoid the two steps: instantiate and fill.

ui.qml

import MyModules 1.0

MyUIRoot {
    id: root

    // all the visual stuff
    Rectangle {
        Text {
            text: root.nodeId // binding should be set up automatically
        }
    }
}

Then you build a MyUIRoot class in c++ in myuiroot.h and myuiroot.cpp that inherits QQuickItem with the functions

public:
    Q_PROPERTY(int nodeId READ nodeId NOTIFY nodeIdChanged);
    int nodeId();
    void setNode(Node* node);

signals:
    void nodeIdChanged();

private:
    Node* _node;

In myuiroot.cpp you have

MyUIRoot::MyUIRoot(QQuickItem *parent) :
    QQuickItem(parent)
{
}

int nodeId()
{
    if (_node)
        // make sure that every valid ID is > 0
        return _node.id();
    else
        return -1;
}

void setNode(Node* node)
{
    if (_node != node)
    {
        _node = node;
        emit nodeIdChanged();
    }
}

Register your component in the main.cpp

qmlRegisterType<MyUIRoot>("MyModules", 1, 0, "MyUIRoot");

and create with

QQmlComponent c(_view->engine(), path);
QObject *o = c.create();
MyUIRoot *item = qobject_cast<MyUIRoot*>(o);
item.setNode(....);

The only other way I see to create visual Qt Quick elements from C++ is to use "Custom Scene Graph Items" where you don't need QML to create the visual item. See http://qt-project.org/doc/qt-5.0/qtquick/qquickitem.html#custom-scene-graph-items.

Add a 2nd constructor and create element from C++

ui.h

#include <QQuickItem>
#include "node.h"

class UI : public QQuickItem
{
    Q_OBJECT
public:
    explicit UI(QQuickItem *parent = 0);
    explicit UI(QQuickItem *parent = 0, Node *node = 0);

signals:

public slots:

private:
    Node* _node;

};

ui.cpp

#include "ui.h"

UI::UI(QQuickItem *parent) :
    QQuickItem(parent)
{
}

UI::UI(QQuickItem *parent, Node *node) :
    QQuickItem(parent)
  , _node(node)
{
    qDebug() << "My ID is" << node->id();
}

Create with

Node *node = new Node();
UI ui(0, node); // or parent QQuickItem instead of '0'
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top