8

Dynamically instantiating a QML object from C++ is well documented, but what I can't find is how to instantiate it with pre-specified values for it's properties.

For example, I am creating a slightly modified SplitView from C++ like this:

QQmlEngine* engine = QtQml::qmlEngine( this );
QQmlComponent splitComp( engine, QUrl( "qrc:/qml/Sy_splitView.qml" ) );
QObject* splitter = splitComp.create();

splitter->setProperty( "orientation", QVariant::fromValue( orientation ) );

The problem I have is that specifying the orientation of the SplitView after it is instantiated causes it's internal layout to break. So, is there a way of creating the SplitView with the orientation already specified?

Alternatively I can create both a horizontal and vertical version of SplitView in separate files and instantiate the appropriate one at runtime - but this is less elegant.

Update

I found QQmlComponent::beginCreate(QQmlContext* publicContext):

QQmlEngine* engine = QtQml::qmlEngine( this );
QQmlComponent splitComp( engine, QUrl( "qrc:/qml/Sy_splitView.qml" ) );
QObject* splitter = splitComp.beginCreate( engine->contextForObject( this ) );

splitter->setProperty( "orientation", QVariant::fromValue( orientation ) );
splitter->setParent( parent() );
splitter->setProperty( "parent", QVariant::fromValue( parent() ) );
splitComp.completeCreate();

But it had no effect surprisingly.

cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • I bet the thing is in how you attempt to assign the enum via QVariant (enums are somewhat buggy in QML). I'd try to first register a simple QObject based type and a custom enum and check if the whole thing works at all. [also note, that you apparently attempt to set parent twice, but that's minor] – mlvljr Oct 31 '13 at 07:53
  • I didn't know that about enums, so thanks I'll try it. And I'm not setting the parent twice, I set the `QObject` parent first, and then QML visual parent second (if I could set the `QObject` parent via QML, I wouldn't have bothered with C++ at all for this). – cmannett85 Oct 31 '13 at 08:24
  • Right, indeed; instead of setting the QObject parent though, you could set memory ownership (or however it is called) to QmlOwnership, I believe (so that the freshly created object will be garbage-colleced / ref-counted by the QML runtime as necessary). Btw, is it that you specifically need to set the QObject parent for some reasons other than memory management? – mlvljr Oct 31 '13 at 11:11
  • 1
    It is for memory management. Each `SplitView` contains two custom OSG viewports, each of which in turn can be split ad infinitum (like Qt Creator's text editor panes), forming a tree. So when I delete a particular splitter, it deletes all of the children appropriately. I'll take a look at having ownership on QML side, it might simplify things - thanks! – cmannett85 Oct 31 '13 at 11:56

3 Answers3

3

For anyone still interested in this problem, in Qt 5 (and so Qt 6), you can also use a custom QQmlContext with QQmlContext::setContextProperty() to setup external property (orientation in your case):

QQmlEngine engine;

QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("myCustomOrientation", QVariant::fromValue(orientation));

// you can use a 'myCustomOrientation' property inside Sy_splitView.qml, e.g.
// `orientation: myCustomOrientation`
QQmlComponent splitComp(&engine, QUrl("qrc:/qml/Sy_splitView.qml"));
QObject* splitter = splitComp.create(context);

This should allow you to not fiddle with beginCreate and completeCreate.

Update:

There is also QQmlComponent::createWithInitialProperties() (from 5.14 onwards) that allows you to create an object and initialize its properties before the creation process finishes.

(And then QQmlApplicationEngine::setInitialProperties() is the application engine's version of the same functionality)

andrgolubev
  • 825
  • 6
  • 21
0

I think you should be able to use a custom QQmlIncubator and the QQmlComponent::create(QQmlIncubator & incubator, QQmlContext * context = 0, QQmlContext * forContext = 0) factory method.

In particular, quoting from the QQmlIncubator documentation:

void QQmlIncubator::setInitialState(QObject * object) [virtual protected]

Called after the object is first created, but before property bindings are evaluated and, if applicable, QQmlParserStatus::componentComplete() is called. This is equivalent to the point between QQmlComponent::beginCreate() and QQmlComponent::endCreate(), and can be used to assign initial values to the object's properties.

The default implementation does nothing.

Community
  • 1
  • 1
bks
  • 1,360
  • 6
  • 7
0

I have had similar situation for my own QML component. Just wanted to init some props before running some bindings. In pure QML I did it that way:

var some = component.createObject(this, {'modelClass': my_model});

From C++ I tried that way:

// create ui object
auto uiObject = qobject_cast<QQuickItem*>(component.beginCreate(ctx));
// place on ui
uiObject->setParentItem(cont);

// set model properties
classInstance->setView(QVariant::fromValue(uiObject));
classInstance->setPosition(QPointF(x, y));

// set ui object properties
uiObject->setProperty("modelClass", QVariant::fromValue(classInstance.get()));

// finish
component.completeCreate();

but I had binding errors because modelClass remains null! After digging for a while I found the cause. And it looks reasonable for me. I've changed my QML class!

Item {
    id: root
    // property var modelClass: null
    property var modelClass // just remove : null!
}

Properties with initial values right after invoking beginCreate are not visible from C++, until I call completeCreate(). But if I remove initial value property becomes visible and I can initialize it in C++ code

Viktor
  • 392
  • 2
  • 8