0

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.

Danik
  • 404
  • 3
  • 14
  • 1
    I'm not sure if it makes sense for your situation, but possibly you can instantiate the facade in main and set it as a contextProperty like you did with the stateObj itself? Another option might be using `stateFacade["myInt"]` – Amfasis Oct 11 '22 at 19:58
  • @Amfasis, thanks! This is exactly how I ended up solving it and it works well. – Danik Oct 13 '22 at 07:43

1 Answers1

0

One of the issues you're dealing with is you have a notion of order:

  1. myStateObj
  2. StateQMLFacade
  3. testText

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:

  1. StateQMLFacade
  2. testText
  3. myStateObj

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.

  1. StateQMLFacade
  2. testText but initially undefined
  3. myStateObj
  4. StateQMLFacade reacts to stateObj change and emits myIntChanged
  5. myIntChanged emit
  6. testText updated again

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 ?? "")
    }
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
  • Thanks. That makes sense, but I'm not sure why it didn't work. My guess is that cannot see the properties that are added later dynamically, and therefore gives up the binding to stateFacade.myInt. The null check didn't help either unfortunately, but I solved it by instantiating the facade object manually in c++ and setting it as the context property instead of the state object, which has the added benefit of not having to instantiate StateQMLFacade manually in each file that needs to access it. – Danik Oct 13 '22 at 07:50