Dynamically reparenting objects in QML fails on initial load

qml, qt

Solution

Easy way of doing it is placing extra `Item` under your delegate. This will cause `Repeater` to reparent `Item` and your own code would set new parent of its child, your `Rectangle` element. Like this:

import QtQuick 1.1
Rectangle {
    id: container
    height: 300
    width: 300

    signal completed

    ListModel {
        id: fooModel
        ListElement { data: "red" }
        ListElement { data: "red" }
        ListElement { data: "blue" }
    }

    Component.onCompleted: {
        console.log("Rect Completed!")
        container.completed()
    }

    // The object I want to dynamically move
    Component {
        id: delg
        Item { 
            Rectangle {
                id: moveable
                height: 40; width: 100
                border.width: 1; border.color: "black"
                state: model.data
                color: state

                // The following code makes it work, but feels very hackish
                /*Connections {
                    target: container
                    onCompleted: {
                        moveable.parent = moveable.state == "red" ? red_col : blue_col
                    }
                }*/

                onStateChanged: { console.log("New state: " + state) }
                onParentChanged: { console.log("New parent: " + parent) }
                Component.onCompleted: { console.log("Delegate Completed!") }


                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        // I know this is bad to do, but in my REAL application,
                        // the change is triggered through the model, not the qml
                        // object
                        moveable.state = (moveable.state == "red" ? "blue" : "red")
                    }
                }

                states: [
                    State {
                        name: 'red'
                        ParentChange { target: moveable; parent: red_col; x: 0 }
                    },
                    State {
                        name: 'blue'
                        ParentChange { target: moveable; parent: blue_col; x: 0 }
                    }
                ]

                transitions: [ Transition {
                        ParentAnimation {
                            NumberAnimation { properties: 'x,y,height,width' }
                        }
                }]
            }
        }
    }

    // Generates the Objects
    Repeater {
        id: repeat
        model: fooModel
        delegate: delg
    }

    // Display
    Row {
        spacing: 100
        Column {
            id: red_col
            spacing: 10
            width: 100; height: 300
            move: Transition { NumberAnimation { properties: "y" } }
            add: Transition { NumberAnimation { properties: "y" } }
        }
        Column {
            id: blue_col
            spacing: 10
            width: 100; height: 300
            move: Transition { NumberAnimation { properties: "y" } }
            add: Transition { NumberAnimation { properties: "y" } }
        }
    }
}

Problem

I am trying to dynamically reparent QML objects generated in a repeater according to data they inherit from their model. This works like a charm - with one catch. When the object is generated for the first time, it is automatically reparented to the Repeater's parent after the state's ParentChange object makes its changes. Run the following QML file in a QML viewer, paying attention to the order of the console messages to see what I'm describing. After you've clicked on each of the objects, they behave as expected. ``` import QtQuick 1.1 Rectangle { id: container height: 300 width: 300 signal completed ListModel { id: fooModel ListElement { data: "red" } ListElement { data: "red" } ListElement { data: "blue" } } Component.onCompleted: { console.log("Rect Completed!") container.completed() } // The object I want to dynamically move Component { id: delg Rectangle { id: moveable height: 40; width: 100 border.width: 1; border.color: "black" state: model.data color: state // The following code makes it work, but feels very hackish /*Connections { target: container onCompleted: { moveable.parent = moveable.state == "red" ? red_col : blue_col } }*/ onStateChanged: { console.log("New state: " + state) } onParentChanged: { console.log("New parent: " + parent) } Component.onCompleted: { console.log("Delegate Completed!") } MouseArea { anchors.fill: parent onClicked: { // I know this is bad to do, but in my REAL application, // the change is triggered through the model, not the qml // object moveable.state = (moveable.state == "red" ? "blue" : "red") } } states: [ State { name: 'red' ParentChange { target: moveable; parent: red_col; x: 0 } }, State { name: 'blue' ParentChange { target: moveable; parent: blue_col; x: 0 } } ] transitions: [ Transition { ParentAnimation { NumberAnimation { properties: 'x,y,height,width' } } }] } } // Generates the Objects Repeater { id: repeat model: fooModel delegate: delg } // Display Row { spacing: 100 Column { id: red_col spacing: 10 width: 100; height: 300 move: Transition { NumberAnimation { properties: "y" } } add: Transition { NumberAnimation { properties: "y" } } } Column { id: blue_col spacing: 10 width: 100; height: 300 move: Transition { NumberAnimation { properties: "y" } } add: Transition { NumberAnimation { properties: "y" } } } } } ``` I figured out a way to fix the behavior, but it's not pretty. (See the commented out "Connections" code above for that fix). Is there a cleaner/less-hacky way to accomplish the same thing I'm trying here?

Original source