Search code examples
qtqml

QML Column: possible QQuickItem::polish() loop


I have two problems with the following code.

  1. If I'm using padding in the QML Column I get this message:

    QML Column: possible QQuickItem::polish() loop
    

    and the application becomes unresponsive. Also if I don't use anchors the problem does not appear, but the Rectangle inside the Column won't be stretched.

  2. If I'm using anchors the Column's implicitWidth and impliciHeight will be zero which will result that the Rectangles won't be shown.

The Qt documentation says this:

Also, since a Column automatically positions its children vertically, a child item within a Column should not set its y position or vertically anchor itself using the top, bottom, anchors.verticalCenter, fill or centerIn anchors.

Which means horizontal anchoring (left/right) is not prohibited.

Any idea what could be wrong?

Rectangle {
    anchors.fill: parent
    color: "green"
    Rectangle {
        anchors.centerIn: parent
        implicitWidth: col.implicitWidth
        implicitHeight: col.implicitHeight
        color: "blue"
        Column {
            spacing: 10
            //padding: 10 // causes: QML Column: possible QQuickItem::polish() loop
            id: col
            Rectangle {
                anchors.left: parent.left
                anchors.right: parent.right
                implicitWidth: 100
                implicitHeight: 25
            }
            Rectangle {
                //anchors.left: parent.left // uncommenting these anchors will result that the column's implicitWidth and implicitHeight will be 0
                //anchors.right: parent.right
                implicitWidth: 200
                implicitHeight: 25
            }
            Component.onCompleted: console.log("column, imp width: " + implicitWidth + ", imp height: " + implicitHeight)
        }
    }
}

Solution

  • As discussed in comments, Ponzifex's code can be improved:

    Remove reparenting and instead create a new default property alias like this:

    Rectangle 
    { 
       default property alias data2: col.data
    
       data: 
       [ 
          Column 
          { 
            id: col; 
            onChildrenChanged: { ...calculate sizes, add bindings, etc... }
          } 
       ]
    } 
    

    How this works:

    • When in QML code you are nesting objects in another object, you are adding them to a property that is marked as default inside the parent object
    • For Item and thus Rectangle the property called data is marked as default
      • That property contains a combined list of visual children and resources of Item / Rectangle
      • Thus normally nesting visual elements in Rectangle causes them to be added as that Rectangle's visual children
      • So normally Rectangle { Text {}; Timer{} } ...
      • ...is equivalent to: Rectangle { data: [ Text {}, Timer{} ] }
    • I changed that by creating a new property called data2 and setting it as default for the Rectangle
      • data2 is not related to data so its elements are not added to Rectangle's visual children list
      • Instead I made data2 an alias to data property of your Column
      • An alias property is just that - an alias - another name of the property, in this case - as a property of another object - but both name "point" to the same actual property an thus Column's list of visual children
      • So all QML elements nested inside the Rectangle are added as visual children of the Column
    • However, now I have a problem: I cannot just nest the Column into Rectangle in QML code because it would mean that the Column needs to be added as it's own child (which makes no sense)
      • So I have to assign Rectangle's actual data property (which is no longer default so I have to write its name explicitly) thus adding the Column as a visual child of the Rectangle
    • Now whenever Rectangle's nested elements are added or removed (including by a Repeater), the Column's data property changes, but since we are adding visual children children property also changes
      • So we can trigger recalculations and rebinding when Column's onChildrenChanged signal fires (or onDataChanged if you want to also trigger on non-visual children aka resources)
      • You can skip elements which already have your bindings or just rebind them

    As far as I know, this is supposed to be valid and supported QML syntax - just not the one you usually use - so it's ok to use it in production code, perhaps with a comment explaining what is happening