0
votes

A property that is bound to an expression is updated when something in the expression changes. This is called a dependency.

EDIT:

To clarify:

  • I'm interested in details on how Qt determines a list of dependencies
  • Dependencies on simple bindings such as x: y are more or less obvious
  • The question is about less obvious cases such as x: myItemId["y"] and x: myFunction(z) where myFunction(p) { if (p) return myItemId.y }

Sometimes QML engine is able to detect change even if the expression is a function call without arguments, other times it cannot do that (for example mapToItem(item,0,0).x).

Another example of imperfection is that setting JS array item value without reassigning the array itself doesn't normally produce onXxxxxChanged signal or update anything referring to that array value.

An expression with unused result (x: {myForcedDependency; return myActualCalculation()}) is sometimes suggested to force a dependency.

According to this KDAB article and Qt source code, a binding expression is not only evaluated but any properties "accessed" during that are "captured" in something called a "guard", then every guard properties onXxxxxChanged() signals are connected, but actual details of this process are unclear.

So my questions are:

  • Are there any defined rules of dependency resolution?

  • How does it really work?

    • How deeply does QQmlEngine/V8 scan "accesses" into functions called by the binding expression and what may prevent it from doing that?
    • Is dependency-detection only based on the first attempt at property resolution?
    • Are all possible code paths checked even if execution never reached there yet?
      • Are non-trivial accesses determined in those cases, such as object["property"] syntax?
      • What if some unexecuted code is (currently) erroneous (and does not produce an error but cannot be properly analyzed)?
  • How can the dependency resolution process be influenced?

    • Is there a way to avoid or block a dependency?
      • As far as I understand an intermediate "filter" property that only actually changes its value when it's necessary to update is the intended way, correct?
    • Is there an intended way to force a dependency?
      • Is manually emitting "XxxxxChanged" signal the correct/supported way to force an update?
      • Is adding an unused reference a legal/intended way to do it or undefined behavior based on the current implementation quirk?

Any information would be useful, although I did read the official documentation on QML properties, QML bindings and JavaScript expressions and didn't find any concrete explanation - if you refer to the official documentation please quote relevant parts.

Please note that I'm not asking you to test if any of this works on your system, but if it's supposed to work - if it can be relied on

1

1 Answers

0
votes

It makes more sense if you just think of bindings as connected signals. If you have something like this:

property int x: y

It's just like doing this in C++:

connect(this, &SomeClass::yChanged, [this]() { x = y; });

The same goes for expressions:

property int x: y + z

would be equivalent to:

connect(this, &SomeClass::yChanged, [this]() { x = y + z; });
connect(this, &SomeClass::zChanged, [this]() { x = y + z; });

And the same with function calls:

property int x: someFunc()
function someFunc() {
    return y;
}

The only time bindings don't update is when there is no onChanged signal to connect to, or the onChanged signal doesn't get emitted for whatever reason.

property int x: cppObject.invokable()

In the above case, the only property that x is able to connect to is cppObject. If invokable references other properties, those won't be connected to x and therefore the binding won't update.

property var array: [1, 2, 3]
property int x: array[0]

function updateArray() {
    array = [2, 4, 6]
    arrayChanged()  // Manually call the onChanged signal to update `x`
}

var properties do not notify by default (for some reason). So in this case, we have to manually call the changed signal, but then the binding will still work.