3
votes

I have a QQuickView that displays a QML file which itself consists of several QML Items (in separate files). I'd like to add Items dynamically using C++ code. The dynamically added item should resize with the parent one, i.e. width and height properties reference parent.

For example, my target Item in QML looks like this:

// TargetContainer.qml

Grid {
    id: fillMeWithItemsContainer
    objectName: "fillMeWithItemsContainer"
}

The Item I want to add dynamically (maybe multiple times) looks like this:

// DynamicItem.qml

Rectangle {
    color: "white"

    height: fillMeWithItemsContainer.height
    width: height * 4/3
}

Note that the rectangle references the container it is intended to reside in regarding height.

quickView is populated with TargetContainer:

QQuickView *quickView = new QQuickView();
quickView->setSource(QUrl("qrc:/foo/bar/TargetContainer.qml"));

So I load a component

QQmlComponent dynamicallyLoadedComponent(
                quickView->engine(),
                QUrl("qrc:/foo/bar/DynamicItem.qml")
                );

And I create an Object out of it.

QObject *dynamicallyLoadedObject = dynamicallyLoadedComponent.create();

Here I get an error (in application output view):

DynamicItem.qml:4: ReferenceError: fillMeWithItemsContainer is not defined

quickView should be aware of the existence of fillMeWithItemsContainer, because it has been created before. However, fillMeWithItemsContainer is not a parent of dynamicallyLoadedObject (yet) and this could be the problem.

So I find the target Item by

QQuickItem *targetItem = quickView->rootObject()->findChild<QQuickItem*>("fillMeWithItemsContainer");

And reparent the previously created object

dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));

Note: I tried dynamicallyLoadedObject->setParent() before, but this seems to be a different kind of parent (QObject vs. parent property).

However, the width and height properties of dynamicallyLoadedObject are set to 0 (because of the reference error, I assume) and won't change. Even if I set them again programatically

dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");

nothing changes.

If I define DynamicItem directly in QML it works:

Grid {
    id: fillMeWithItemsContainer
    objectName: "fillMeWithItemsContainer"

    DynamicItem {}
}

How do I make sure that dynamically added items can access Items that have been in the QML view before? Alternatively: What am I doing wrong?

2

2 Answers

5
votes
dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");

This will not actually set a JavaScript binding on the properties. Instead, it will try to assign e.g. the string "fillMeWithItemsContainer.height;" to the property, which will fail since the property is of type int, not of type QString. Assigning bindings to properties is actually not possible from within C++ (with some exceptions like QQmlBinding).

dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));

As Sergei mentioned, you need to call QQuickItem::setParentItem instead of setting the parent property. That is also a bit more typesafe than the general string-based setProperty API. Without a parent item, a QQuickItem will not be visible. Reparenting will only change the parent item, which will affect layouting and a few other things. It will not change the context of the object. A context defines which objects/IDs are in the scope. The context can not be changed after an item has been created. Even if changing the parent would change the context, it is too late - the object has been created, and IDs/object are only looked up in the creation phase.

The solution is to pass the correct context to QQmlComponent::create(), which actually has an optional argument. You need to create your item in the context of fillMeWithItemsContainer, so you need to get a pointer to it (you did that already with findChild) and then retrieve its context, which is possible with QQmlEngine::contextForObject(). That should give you enough to figure out how to make it work.

I agree with Sergei though, you should prefer to dynamically create objects in JavaScript instead. Changing QML from within C++ is a layering violation, you should never access QML from C++, only the other way around, to have a nicer separation between UI and program logic.

1
votes

I believe, QML engine treats DynamicItem instance as a non-graphical item since it is casted to QObject*. Thus it is not rendered. It has to be at least QQuickItem* to be rendered.

I believe you experience same problem with setParent() since parent property refers to parent QQuickItem and might no be the same as QObject parent.

Two questions:

  1. Why wouldn't you create dynamic objects in JS?
  2. Is it possible to use relative parent instead of absolute fillMeWithItemsContainer?

p.s. I assume you understand this is rather irregular way of using QML and have strong reasons for such hacky approach.