Reputation: 404
I'm implementing a system for syncing data from and to QML and QtScript using a QObject as the backing model and facade objects in QML, QtScript and C++, to simplify data synchronization between different subsystems.
The idea is that one could instantiate a facade object in QML and set a reference to the model QObject, and the facade gets properties added dynamically to allow binding to them in QML. So far it's working quite well, with one problem. The setter for the model QObject gets called too late, which makes bindings fail. If the bindings are done in Component.onCompleted instead it works as expected:
StateQMLFacade {
id: stateFacade
stateObj: myStateObj; // accessible as a context property
}
Text {
id: testText
text: "myInt: " + stateFacade.myInt // Prints "myInt: undefined"
Component.onCompleted: {
// With this line the binding works as expected
testText.text = Qt.binding(function() { return "myInt: " + stateFacade.myInt })
}
}
The facade item is a subclass of QQmlPropertyMap, and gets all properties of the model object added dynamically in the WRITE function for the property stateObj, and connects notify signals in both directions to sync the two. Which is why the properties do not exist when the bindings are attempted before stateObj is set.
The QML is created using QQmlComponent::create(QQmlContext* context)
When investigating further I found that setters for QObject* properties get called after all other bindings are done. For some reason properties that are integers etc. get set in QQmlComponentPrivate::doBeginCreate()
, while properties that are QObject*'s get set in QQmlComponentPrivate::completeCreate()
One way to solve it is to use a delayed Qt.binding like in the example above, or delay the instantiation of the components that bind to the facade object, but I want the mechanism to be seamless for the users. It's all working well except for this last timing/order issue.
I am wondering if there is any way to get more control over the order things are initialized in, or if the issue can be solved in some other way? Maybe it's possible to instantiate and initialize the facade early, and somehow inject it into the other QML?
This is using Qt version 5.15.
Any help and insight is appreciated.
Upvotes: 0
Views: 378
Reputation: 26309
One of the issues you're dealing with is you have a notion of order:
However, when you are doing property binding, you can never be sure that things will be initialized in that prescribed order and once. So, imagine the property binding went in this order:
You will need to ensure that your components deal with the initialization state and may go thru periods of temporary undefined values, and, as well as that, omit corresponding signals so that the thing finally settles, i.e.
Lastly, the undefined
state can stop the property binding to continue to evaluate, so, it's best to absorb undefined values with default values, e.g.
Text {
id: testText
text: "myInt: " + (stateFacade.myInt ?? "")
}
Upvotes: 0