How to add a property to a QML object after creation but before binding evaluation?
-
Let's assume the following QML document:
Project { Testcase { name: "test1" Exports { property string result } run: { result = "yes" } } Testcase { name: "test2" Depends { name: "test1" } property someProperty: test1.result /* ... */ }
I am trying to attach the
Exports
item from the firstTestcase
object to the secondTestcase
object under the nametest1
so that the binding ofsomeProperty
becomes valid. Note: I am explicitly NOT usingid:test1
because in my DSL theTestcase
objects could be defined in separate documents. The idea is a bit similar to Product items in Qbs, but the meaning and implementation is very different. And again: This is not a normal QML application. This document is analysed and heavily manipulated before it is executed by QQmlEngine.I thought I'd have the possibility to do add properties to objects after
Component::beginCreate()
, but that doesn't work:- Calling
QObject::setProperty("test1", someObject)
on test2 has no effect, even before
QQmlComponent::completeCreate()
. - Using
QQmlProperty
on test2 has no effect, probably because the property
doesn't physically exist. - Setting test1 as context property of
qmlContext(test2)
fails because the context is private. Setting it on the parent context of test2 would bring it into the root context which gives it wrong scope. I wanttest1
to point to my object only within the scope oftest2
.
So I wonder:
-
Is there any possibility to add a property to an object after
QQmlComponent::beginCreate()
and before bindings are evaluated inQQmlComponent::completeCreate()
? -
Why does every QML object have a private context that is not writable? I've asked this question already here, but didn't get any response.
I am looking forward for hints and answers.
Richard - Calling
-
@rweickelt said in How to add a property to a QML object after creation but before binding evaluation?:
test1.result
Where is
test1
defined? If you think it is this:Testcase { name: "test1" ... }
then you are mistaken. If you declare it like:
Testcase { id: test1 name: "test1" property string someStringProperty ... }
Then you can use
test1.someStringProperty
-
Thank you for your reply. I think my question was not clear enough. This is not a normal QML application. Instead, the document is analysed and manipulated before the QML engine executes it.
The reason why I cannot use
id:test1
is thatTestcase
objects might be defined in separate documents. My backend analyses all documents and based on theDepends
items and theExports
items, it creates relationships.So if any test case contains an item
Depends { name: "bla" }
AND the test case "bla" contains anExports { }
item, then thisExports
item of"bla"
is attached to the dependent test case using the name "bla".Does that make it clear?
-
@rweickelt
OK... my bad. I never usedTestCase
s and the Qbs framework, hence my confusion. Yet another thing for me to learn... -
Never mind. It's still QML after all and in difference to Qbs I am using a vanilla QQmlEngine. I am just analyzing and manipulating the object tree between
QQmlComponent::beginCreate()
andQQmlComponent::completeCreate()
.Still looking for a solution to my problem.
-
Here is a minimal almost working example that shows the same issue:
file.qml:
import QtQml 2.0 QtObject { property var test1: QtObject { objectName: "child1" property string color: environment.color } property var test2: QtObject { objectName: "child2" property string color: environment.color } }
main.cpp:
#include <QtCore> #include <QtQml> int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); QQmlEngine engine; QQmlComponent component(&engine,QUrl::fromLocalFile("file.qml")); QObject* object = component.beginCreate(engine.rootContext()); QObject* child1 = object->findChild<QObject*>("child1", Qt::FindChildrenRecursively); QObject* child2 = object->findChild<QObject*>("child2", Qt::FindChildrenRecursively); QQmlPropertyMap env1; QQmlPropertyMap env2; env1["color"] = "green"; env2["color"] = "blue"; // Now I want to make object1 available for child1 // and object2 available for child2. // But I can't do this: // qmlContext(test1)->setContextProperty("environment", env1); // qmlContext(test2)->setContextProperty("environment", env2); component.completeCreate(); if (!component.errors().isEmpty()) { qDebug() << component.errorString(); } Q_ASSERT(child1->property("color") == QString("green")); Q_ASSERT(child2->property("color") == QString("blue")); qDebug() << "It works"; }
That might be more similar to a normal QML application.
-
What about something like:
#include <QtCore> #include <QtQml> #include <QQmlContext> #include <QVariant> #include <QStringList> int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); QQmlEngine engine; QQmlComponent component(&engine,QUrl::fromLocalFile("G:/UMAGI_Source/Qt/test6/file.qml")); QObject* object = component.beginCreate(engine.rootContext()); QObject* child1 = object->findChild<QObject*>("child1", Qt::FindChildrenRecursively); QObject* child2 = object->findChild<QObject*>("child2", Qt::FindChildrenRecursively); QQmlPropertyMap env; env["colors"] = QVariant(QStringList({"green", "blue"})); QQmlContext *ctxt = engine.rootContext(); ctxt->setContextProperty("environment", &env); component.completeCreate(); if (!component.errors().isEmpty()) { qDebug() << component.errorString(); } Q_ASSERT(child1->property("color") == QString("green")); Q_ASSERT(child2->property("color") == QString("blue")); qDebug() << "It works"; }
and
import QtQml 2.11 QtObject { property var test1: QtObject { objectName: "child1" property string color: environment.colors[0] } property var test2: QtObject { objectName: "child2" property string color: environment.colors[1] } }
This gives:
04:08:38: Starting G:\Source\Qt\build-test6-Desktop_Qt_5_11_0_MSVC2017_64bit3-Debug\debug\test6...
QML debugging is enabled. Only use this in a safe environment.
It works -
Indeed. Thank you for taking the time. But the person who writes the documents is not supposed to know the index of the correct element. The env objects in the above example are not randomly assigned.
What I now ended up with, is a
QQmlPropertyMap
property in myTestcase
item under the name "dependencies". Speaking in the terms of my original post: For eachDepends
item, I am now inserting a QVariantMap that represents theExports
item. That gives me unnecessary verbose QML code, but at least, it solves the problem:Project { Testcase { name: "test1" Exports { property string result } run: { result = "yes" } } Testcase { name: "test2" Depends { name: "test1" } property someProperty: dependencies.test1.result /* ... */ }
I might also experiment with sub-classing
Testcase
fromQQmlPropertyMap
so that I can skip the intermediatedependencies
.