2

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.

Simon Warta
  • 10,850
  • 5
  • 40
  • 78
dtech
  • 47,916
  • 17
  • 112
  • 190
  • How exactly do you instantiate your QML components from C++? Why can't you have parameters in the constructor? – Simon Warta Jan 11 '14 at 00:15
  • @SimonWarta - `QQmlComponent c(_view->engine(), path); QObject * o = c.create();` - the component is instantiated by the QML factory from a path string, not possible to specify constructor parameters. – dtech Jan 11 '14 at 00:23
  • FYI: the virtual method componentComplete() is called when objects are finished being "created" in QML. I use a flag that allows me to delay initialization until the object is fully initialized. I haven't figured out how to control when that's called yet when instantiating from C++ but it might be worth investigating. – Jay Dec 20 '14 at 11:52
  • @Jay - yes, later I resorted to create the object, set properties and then call `completeCreate()` which is helpful for situations where you don't need to set something as early as in the construction stage. – dtech Dec 20 '14 at 13:06

3 Answers3

1

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

dtech
  • 47,916
  • 17
  • 112
  • 190
0

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.

Simon Warta
  • 10,850
  • 5
  • 40
  • 78
  • This is exactly what I am doing right now: `QQmlComponent c(_view->engine(), path); QObject * o = c.create(); _ui = qobject_cast(o); _ui->setObject(this);` HOWEVER it is too late to set the `Node*` after the `UI` instantiation, I need to be able to set the `Node*` in the very constructor of `UI` so the instantiated object can immediately access node members. Is there any other way to instantiate a `QQuickItem` with a custom constructor and not through `QQmlComponent::create()`? – dtech Jan 11 '14 at 10:03
  • How complex is your visual `UI` component? I could imagine that you can build it completely in C++ with `QQuickItem`s and its list of children. – Simon Warta Jan 11 '14 at 10:09
  • `UI` us derived from `QQuickItem` and is 100% written in C++. It doesn't have its own visual representation, I use it like QML's `Item` component - one that draws nothing but is still regarded as a visual element, e.g. has coordinates and all the properties needed to position it. Which is the reason it is derived from `QQuickItem` and not from `QObject` directly. – dtech Jan 11 '14 at 10:18
  • Th`ID` property is just a tiny part of the problem, the actual object actually has a load of properties and also is responsible for the positioning of the visual representation. Not being able to have access to `Node` right away creates a huge issue that requires an additional update cycle of the entire hierarchy for the correct state to take place. I've managed to "walk around" the problem of not being able to set the mode in the constructor a bit, but it is very ugly and comes at a cost. The only solution is to instantiate `UI` like a normal C++ class and set the node with the constructor. – dtech Jan 11 '14 at 10:32
0

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'
Simon Warta
  • 10,850
  • 5
  • 40
  • 78
  • Create the UI on the stack? – dtech Jan 11 '14 at 10:42
  • Also, there is a catch - in my design, I never really instantiate `UI` alone, it is just the functional base for different QML components which use it as a root object (`path` is generated procedurally from a Q_ENUM based on the node subclass type). So it makes me absolutely no good to create a `UI` per se, I need to be able to create arbitrary QML files which call have a UI element as a root for each node to use and specify the node for the constructor to set, which is solved in the answer I posted a while ago. – dtech Jan 11 '14 at 10:53