I notice that QML has some unspecified problem with the following - when a delegate tries to instantiate an object that contains it (the delegate that is).
Consider this trivial tree builder example:
// Object.qml
Row {
property int c : 0
Rectangle {
width: 50
height: 50
color: "red"
border.color: "black"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) c++
else if (c) c--
}
}
}
List { model: c }
}
// List.qml
Column {
property alias model : r.model
Repeater {
id: r
delegate: Object {} // wont work
}
}
In this example Object
contains List
, which has a delegate that instantiates another Object
. However it doesn't work, there are no error messages, not even in debug mode, the application window simply doesn't appear, and from the looks of memory usage, it is hopelessly stuck somewhere in oblivion. It doesn't crash either. It just doesn't work.
My first suspicion is that this is yet another one of QML's shortsighted implementation details, with the noble goal to protect against accidental infinite recursions. However, in this case there is no danger of such, because even though the two objects mutually instantiate each other, the process is controlled by the "model".
It also happens to work "flawlessly" if an "indirection" is added, for example:
Column {
property alias model : r.model
Repeater {
id: r
delegate: Loader { source: "Object.qml" }
}
}
If the delegate is replaced from an Object
to a Loader
that instnatiates an Object
, it works OK, I assume because it doesn't trigger the dumb cross-reference check which interrupts execution.
You might be asking why I simply don't use the second scenario, and the answer is simple - it is trivial example it is OK, but in my actual production code this breaks something very complex I am making with cascading the amount of children items in negative xy space, and inserting a Loader
in between simply kills it, and I haven't found a way to get it to work.
I also notice delegates have that absolutely identical problem when instantiating the tree example from Component
s within that same source, cross-reference or self-reference - when this type of instantiation recursion is used, the application gets stuck the same way - no errors, no warnings, no crash, no window, no nothing.
Any idea what's going on and how to fix it? My money is on infinite recursion protection, which assumes it is infinite even if it is in fact controlled and very much finite recursion.
EDIT: Also note that wrapping it this way doesn't work either delegate: Component { Object {} }
. It works only if Object
is not referenced as an instance, so it looks like some very superficial check if objects mutually contain each other regardless of the actual circumstances. So as expected, it also works if the wrapping is moved to Object
:
Loader {
Component.onCompleted: setSource("List.qml", {"model": Qt.binding(function() {return c})})
}
UPDATE:
I also noticed this:
// Obj.qml
Item {
id: main
property Obj thisObj : main
}
Which doesn't work either. This time there is some actual output in the console, saying that Obj is instantiated recursively
which it really isn't since it is used in the context of a type property, not a value/instance.
It seems that the recursive instantiation checks in QML and fundamentally broken, they just produce absolutely nothing if a literal QML element type self/cross reference is found in the body of an object tree, and produce false output when it the reference is used to specify a property type, even if QML object properties are actually implemented as references rather than instances.
I also issued a bug report and proposing how to distinguish between actual infinite recursions and cases of self or cross reference which are actually not infinite recursions, which is quite simple - simply don't trigger if the reference is a property type or inside a delegate.