Thane Brimhall
Thane Brimhall

Reputation: 9555

Dynamically reparenting objects in QML fails on initial load

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?

Upvotes: 4

Views: 3097

Answers (1)

Dotti
Dotti

Reputation: 791

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" } }
        }
    }
}

Upvotes: 3

Related Questions