qtqmlqtquick2qt5.7

Double-Initialisation of values in QML


I have a strange problem, that is burried somewhere within a large project. So far I was not able to reproduce it in a MCVE, but as soon as I succeed, I will turn it in.

It is quite a simple missbehavior. Basically I have an QtObject with properties, that I set with initial values as such:

TestObj.qml

QtObject {
    id: root
    property int val1: { console.log('set val', root); return 42 }
    Component.onCompleted: console.log('Constructed Object', this)
}

!!! With this example, I do not reproduce the error !!!

The output in my project would be now:

set val TestObj_QMLTYPE_44(0x33799fa8)
set val TestObj_QMLTYPE_44(0x33799fa8)
Constructed Object TestObj_QMLTYPE_44(0x33799fa8)

So, though the object is only created once, the initial property assignment is performed twice.
As I have no idea, where to look for the culprit, I can't produce a reproducable example, but maybe someone stumbled uppon the same situation already and found a solution.

A solution would be beneficial, as this issue results in multiple instantiations of some objects, that I can not destroy.


Solution

  • Created Bug-Report: maybe they find a way to solve this issue withouth hacky workarounds

    The problem are circular references:

    Circular references are resulting in a strange behavior when creating objects.

    TestObj1.qml

    import QtQuick 2.0
    QtObject {
        property Item paps
        property int myVal: { console.log('myVal'); paps.val }
    }
    

    TestObj2.qml

    import QtQuick 2.0    
    Item {
        id: root
        property int val: { console.log('set val', root); return 42 }
        Component.onCompleted: console.log('Constructed Object')
    
        TestObj1 {
            id: to1
            paps: root
        }
    }
    

    Result:

    qml: myVal
    qml: set val TestObj_QMLTYPE_4(0x2c0bafb0)
    qml: set val TestObj_QMLTYPE_4(0x2c0bafb0)
    qml: Constructed Object

    The probalbe cause for this is, that the statement {console.log('set val', root); return 42 } has not been processed, when it is allready assigned to myVal, therefore that statement is executed twice.

    While this is no problem in normal situations, it might lead to problems, as long as we don't have dynamically created objects in those properties.

    TestObj3.qml

    import QtQuick 2.0
    
    Item {
        id: root
        property QtObject obj: { console.log('set obj'); return objPrototype.createObject(root) }
        Component.onCompleted: console.log('Constructed Object', obj)
    
        TestObj4 {
            id: to1
            paps: root
        }
    
        Component {
            id: objPrototype
            QtObject {
                id: op
                Component.onCompleted: console.log('PropertyObject created', op)
            }
        }
    }
    

    TestObj4.qml

    import QtQuick 2.0
    QtObject {
        property Item paps
        property QtObject myObj: paps.obj
    
        Component.onCompleted: console.log(myObj)
    }
    

    Result:

    qml: set obj
    qml: PropertyObject created QObject(0x2c124708)
    qml: set obj
    qml: PropertyObject created QObject(0x2c1246f8)
    qml: Constructed Object QObject(0x2c1246f8)
    qml: QObject(0x2c1246f8)

    So you can see, the object is indeed created twice.

    There are some workarounds:

    1. Don't use circular references - but that would be booring.
    2. Don't use dynamic object creation, as it always produces problems - I could just create the object, and either use property alias obj: myObjectID or property QtObject obj: myObjectID. This has the downside, that if the component is reusable, that the user can't replace that object or the object is still created, wasting memory.
    3. Assign the property in Component.onCompleted if the property is still empty (not overwritten by the user of the reusable component). This has the downside, that the object is not available, uppon creation and a lot of Can't read property ... of null errors will appear. They will hopefully don't break the app though.
    4. Create a nasty binding loop Write: property QtObject obj: (obj ? obj : objPrototype.createObject(root)). It will throw a warning, but c'mon. It's just a binding loop, that will be detected and broken.

    Nothing nice in those workarounds, but maybe something usable.