Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to get ScrollView to scroll when children are reparented into it?



  • I'm reparenting a TextArea into a ScrollView - somewhat unsuccessfully, because the said ScrollView then refuses to scroll.

    What is the right way to go about it? I've stripped the problem down to a small, reproducible usecase here: https://transfer.sh/rYfth/dodgy-scrollview.zip

    And here is the qml file inside that zip in full:

    import QtQuick 2.7
    import QtQuick.Controls 2.5
    import QtQuick.Controls.Material 2.0
    import QtQuick.Window 2.10
    import QtQuick.Controls.Universal 2.12
    import QtQuick.Layouts 1.12
    import QtQuick.Dialogs 1.3
    
    ApplicationWindow {
        id: window
        visible: true
        width: 640; height: 480
        minimumWidth: 550; minimumHeight: 300
        title: qsTr("Hammer")
    
        Page {
            id: addResourcesPage
            width: window.width
            height: window.height - buttonsRow.height
    
    
            ScrollView {
                id: addResourceScrollView
                height: parent.height; width: parent.width;
                visible: textArea.state === "EXPANDED"
                clip: true
    
                onContentHeightChanged: console.log(`content height changed`)
                onContentChildrenChanged: console.log(`children changed; now have: ${children.length}`)
                onChildrenRectChanged: console.log(`children rect changed`)
            }
    
            Label {
                id: hammerLabel
                anchors.horizontalCenter: parent.horizontalCenter
                y: 120
                text: qsTr("🔨 Hammer")
                font.bold: true
                opacity: 0.6
                font.pointSize: 36
                font.family: "Apple Color Emoji"
                visible: textArea.state === "MINIMAL"
            }
    
            Row {
                id: loadResourcesRow
                y: hammerLabel.y + 80
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: 10
    
                Button {
                    id: loadResourceButton
                    text: qsTr("Button")
                    visible: textArea.state === "MINIMAL"
                }
    
                TextArea {
                    id: textArea
                    placeholderText: qsTr("or load it here")
                    renderType: Text.NativeRendering
                    // ensure the tooltip isn't monospace, only the text
                    font.family: text ? "Ubuntu Mono" : "Ubuntu"
                    selectByMouse: true
                    wrapMode: "WrapAtWordBoundaryOrAnywhere"
    
                    // ensure focus remains when the area is reparented
                    onWidthChanged: textArea.forceActiveFocus()
                    // it doesn't have focus on startup
                    Component.onCompleted: textArea.focus = false
    
                    states: [
                        State {
                            name: "MINIMAL"; when: !textArea.text
                            ParentChange {
                                target: textArea
                                parent: loadResourcesRow
                                width: 300
                                height: undefined
                            }
                        },
                        State {
                            name: "EXPANDED"; when: textArea.text
                            ParentChange {
                                target: textArea
                                parent: addResourceScrollView
                                x: 0; y: 0
                                width: window.width
                                height: window.height
                            }
                        }
                    ]
                    state: "MINIMAL"
    
                    transitions: Transition {
                        ParentAnimation {
                            NumberAnimation { properties: "x,y,width,height";  easing.type: Easing.InCubic; duration: 600 }
                        }
                    }
                }
    
            }
    
            Text {
                id: experimentalText
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: qsTr("Experimental")
                enabled: false
                z: 0
                rotation: -45
                opacity: 0.1
                font.pixelSize: 96
            }
    
        }
    
        RowLayout {
            id: buttonsRow
            x: 0
            y: parent.height - height
            width: window.width
    
            Button {
                id: settingsButton
                text: "☰"
    
                onClicked: addResourcesPage.state = "EDITING_SETTINGS"
            }
    
            Button {
                id: actionButton
                text: "stuff"
                visible: textArea.text || addResourcesPage.state === "EDITING_SETTINGS"
                Layout.fillWidth: true
            }
        }
    }
    

    I suspect ParentChange { width: window.width; height: window.height } is at fault here - I've played around with a lot of values there + in the ScrollView but nothing is working.


  • Qt Champions 2018

    Do this :

    State {
        name: "EXPANDED"; when: textArea.text
        ParentChange {
            target: textArea
            parent: addResourceScrollView.contentItem.contentItem
            x: 0; y: 0
            width: window.width
        }
    }
    

    You don't need to set the height since you don't want it to be constrained.

    As for parent: addResourceScrollView.contentItem.contentItem:
    addResourceScrollView.contentItem is the default Flickable of the ScrollView, and you want to put your TextArea in the contentItem of the Flickable. Personnaly I would have directly used a Flickable instead.



  • Aha, thanks,I would have never thought of using that parent. I'll read up on Flickable, I've been ignoring it because I focus on desktop platforms.

    ScrollView now scrolls as it should, however reverting to the previous state does not go exactly to plan:

    0_1558614347668_Peek 2019-05-23 14-24.gif

    What would be the issue? I'm at a loss.



  • Hi @Vadi2 , it seems to be working fine in my case, have made any changes to the code which you had posted?



  • No, not at all! I've just double-checked by taking the above .qml and replacing the single State within it. Same issue as in the gif, using Qt 5.12.2.



  • Hi @Vadi2
    Your code works fine.

    I just changed the state of TextArea to test on button click to

    Button {
                id: settingsButton
                text: "☰"
    
                onClicked: {
                    textArea.state = "MINIMAL"
                    addResourcesPage.state = "EDITING_SETTINGS"
                }
            }
    

    And the contents are back to their original places.
    0_1558673206248_14a0953b-3fb4-443f-bd29-75086d3404d7-image.png



  • No luck still, somehow! I'm confused.

    0_1558674547397_Peek 2019-05-24 07-08.gif



  • Hi @Vadi2 , can you share your latest complete code, because for me its working fine.





  • @Vadi2 can you try it with Button click as i tested ?
    its fine on Windows & Qt 5.12.2



  • Yes, the latest gif was with the button click. I'll test the qml file on Windows as well, just to be sure...



  • 0_1558677270584_Peek 2019-05-24 11-23.gif

    Your code only from the above post.



  • Windows has the same result :(

    0_1558677854783_Peek 2019-05-24 07-53.gif



  • Hi @Vadi2 , you need to specify height and width to the Row.

    I have specified this and it works fine for me:-

    width: hammerLabel.width
    height: hammerLabel.height
    

    And one more suggestion, when you enter the text you reparent it to the ScrollView and once it gets reparented to the ScrollView you need to again click on the TextArea to edit the Text, if you want to continue to edit the TextArea even when you reparent it , you can use the onParentChanged signal. So instead of using onWidthChanged, you can use onParentChanged like this:

    onParentChanged: textArea.forceActiveFocus()


  • @Shrinidhi-Upadhyaya Cool.
    @Vadi2 Try this.



  • Hm, it kind of works - but I want the Row to auto-size the the contents of its children, I don't want to force it into an explicit width/height.

    Also the TextArea seems to lose it's border? You can see it in https://ddgobkiprc33d.cloudfront.net/8b195d8c-bd58-4dc1-ba36-9c3a10e7aba2.png :(

    Thanks for all the help so far, really appreciate it.



  • Hi @Vadi2

    Is this what you expected ?

    0_1558689491473_Test.gif



  • So close!

    The textArea's height would be window.height - buttonsRow.height - that is, not overlap the buttons below. It should also only resize on the presence of text, not via the ☰ button (it's actually a settings button).

    How did you manage to get it to size back down okay?



  • Hi @Vadi2 , i guess there is some problem with ScrollView.

    1. Problem 1 (Sample Code)

    TextArea {
                    id: textArea
    
                    height: 100
                    width: 100
                    anchors.left: view.right
                    anchors.leftMargin: 100
                }
    
                ScrollView {
                    id: view
    
                    height: 100
                    width: 100
                    clip: true
    
                    TextArea {
                        id: textArea1
    
                        width: parent.width
                        //####Uncomment the below line the,there will be no border line
                        wrapMode: "WrapAtWordBoundaryOrAnywhere"
                    }
                }
    

    I dont knoe how but TextArea inside the ScrolllView behaves weird, if you set wrapMode property, the border does not appear, but if dont set it ,border appears but does not take width specified.

    2. Problem 2 (Sample Code)

    ScrollView {
            id: view
    
            height: 100
            width: 100
            clip: true
        }
    
        Rectangle {
            id: redRect
    
            height: 100
            width: 100
            color: "transparent"
            border.color: "red"
            anchors.centerIn: parent
    
            TextArea {
                id: textArea
    
                width: parent.width
                parent: text
                        ? view.contentItem.contentItem
                        : redRect
    
                onParentChanged: {
                    textArea.forceActiveFocus()
                }
            }
        }
    

    here if you enter the text , you will see that TextArea changes its parent to ScrollView,after that if you delete the text,it should come back to Rectangle, but you will see that borderLine of TextArea remains in the ScrollView,but the text comes back to Rectangle(Weird)

    So as @GrecKo suggested the best solution would be to use Flickable:-

    Here is the sample code:-

    Flickable {
                    id: view
    
                    height: 100
                    width: 200
                    clip: true
                    flickableDirection: Flickable.VerticalFlick
                    contentHeight: textArea.contentHeight
                    contentWidth: 200
                }
    
                Rectangle {
                    id: redRect
    
                    height: 100
                    width: 100
                    color: "red"
                    anchors.centerIn: parent
    
                    TextArea {
                        id: textArea
    
                        width: parent.width
                        wrapMode: "WrapAtWordBoundaryOrAnywhere"
                        anchors.centerIn: parent
                        parent: text?view.contentItem:redRect
                    }
                }


  • @Vadi2
    Yes, Height should be window height - row height from which you can avoid the overlap.

    Just remove the

    textArea.state = "MINIMAL"
    

    from Button clicked to avoid the behavior (I did just to test the code)

    I Used the Flickable { } in place of ScrollView {}. If you want the ScrollBar you can add it to the Flickable {}

    Also now parent will change as its Flickable {} directly

    State {
                            name: "EXPANDED"; when: textArea.text
                            ParentChange {
                                target: textArea
                                parent: addResourceScrollView.contentItem
                                x: 0; y: 0
                                width: window.width
                                height: window.height
                            }
                        }
    

    And also in this case you don't have to set the width & height for the Row {}



  • @Vadi2
    There is an explanation from @Shrinidhi-Upadhyaya with sample code.
    You can check the code once by using his examples.

    Below is height fix for your TextArea

    0_1558691570893_Test2.gif



  • I've applied the changes, but I can't see any text anymore in the expanded state - did I mess up somewhere?

    import QtQuick 2.7
    import QtQuick.Controls 2.5
    import QtQuick.Controls.Material 2.0
    import QtQuick.Window 2.10
    import QtQuick.Controls.Universal 2.12
    import QtQuick.Layouts 1.12
    import QtQuick.Dialogs 1.3
    
    ApplicationWindow {
        id: window
        visible: true
        width: 640; height: 480
        minimumWidth: 550; minimumHeight: 300
        title: qsTr("Hammer")
    
        Page {
            id: addResourcesPage
            width: window.width
            height: window.height - buttonsRow.height
    
    
            Flickable {
                id: addResourceFlickable
                contentHeight: textArea.contentHeight; width: parent.width;
                visible: textArea.state === "EXPANDED"
                clip: true
                flickableDirection: Flickable.VerticalFlick
            }
    
            Label {
                id: hammerLabel
                anchors.horizontalCenter: parent.horizontalCenter
                y: 120
                text: qsTr("🔨 Hammer")
                font.bold: true
                opacity: 0.6
                font.pointSize: 36
                font.family: "Apple Color Emoji"
                visible: textArea.state === "MINIMAL"
            }
    
            Row {
                id: loadResourcesRow
                y: hammerLabel.y + 80
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: 10
    
                Button {
                    id: loadResourceButton
                    text: qsTr("Button")
                    visible: textArea.state === "MINIMAL"
                }
    
                TextArea {
                    id: textArea
                    placeholderText: qsTr("or load it here")
                    renderType: Text.NativeRendering
                    // ensure the tooltip isn't monospace, only the text
                    font.family: text ? "Ubuntu Mono" : "Ubuntu"
                    selectByMouse: true
                    wrapMode: "WrapAtWordBoundaryOrAnywhere"
    
                    // ensure focus remains when the area is reparented
                    onParentChanged: { textArea.forceActiveFocus(); }
    
                    states: [
                        State {
                            name: "MINIMAL"; when: !textArea.text
                            ParentChange {
                                target: textArea
                                parent: loadResourcesRow
                                width: 300
                                height: undefined
                            }
                            PropertyChanges {
                                target: loadResourcesRow
                                y: hammerLabel.y + 80
                                x: 0
                            }
                        },
                        State {
                            name: "EXPANDED"; when: textArea.text
                            ParentChange {
                                target: textArea
                                parent: addResourceFlickable.contentItem
                                x: 0; y: 0
                                width: window.width
                                height: window.height
                            }
                        }
                    ]
                    state: "MINIMAL"
    
                    transitions: Transition {
                        ParentAnimation {
                            NumberAnimation { properties: "x,y,width,height";  easing.type: Easing.InCubic; duration: 600 }
                        }
                    }
                }
    
            }
    
            Text {
                id: experimentalText
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: qsTr("Experimental")
                enabled: false
                z: 0
                rotation: -45
                opacity: 0.1
                font.pixelSize: 96
            }
    
        }
    
        RowLayout {
            id: buttonsRow
            x: 0
            y: parent.height - height
            width: window.width
    
            Button {
                id: settingsButton
                text: "☰"
    
                onClicked: {
                    textArea.state = "MINIMAL"
                    addResourcesPage.state = "EDITING_SETTINGS"
                }
            }
    
            Button {
                id: actionButton
                text: "stuff"
                visible: textArea.text || addResourcesPage.state === "EDITING_SETTINGS"
                Layout.fillWidth: true
            }
        }
    }
    


  • Hi @Vadi2 , you need to specify the height for the flickable, you have missed it.
    Add inside flickable:-

    height: parent.height


  • @Vadi2

    Below is the code. Yes as @Shrinidhi-Upadhyaya mentioned You did miss the height in

    Flickable {}
    

    Below is the code.

        Page {
            id: addResourcesPage
    
            width: window.width
            height: window.height - buttonsRow.height
    
            Flickable {
                id: addResourceFlickable
    
                clip: true
                width: parent.width;
                height: parent.height;
                contentHeight: textArea.contentHeight;
                visible: textArea.state === "EXPANDED"
                flickableDirection: Flickable.VerticalFlick
            }
    
            Label {
                id: hammerLabel
    
                y: 120
                anchors.horizontalCenter: parent.horizontalCenter
                text: qsTr("🔨 Hammer")
                opacity: 0.6
                visible: textArea.state === "MINIMAL"
                font {
                    bold: true
                    pointSize: 36
                    family: "Apple Color Emoji"
                }
            }
    
            Row {
                id: loadResourcesRow
    
                y: hammerLabel.y + 80
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: 10
    
                Button {
                    id: loadResourceButton
    
                    text: qsTr("Button")
                    visible: textArea.state === "MINIMAL"
                }
    
                TextArea {
                    id: textArea
    
                    placeholderText: qsTr("or load it here")
                    renderType: Text.NativeRendering
                    // ensure the tooltip isn't monospace, only the text
                    font.family: text ? "Ubuntu Mono" : "Ubuntu"
                    selectByMouse: true
                    wrapMode: "WrapAtWordBoundaryOrAnywhere"
    
                    states: [
                        State {
                            name: "MINIMAL"; when: !textArea.text
                            ParentChange {
                                target: textArea
                                parent: loadResourcesRow
                                width: 300
                                height: undefined
                            }
                            PropertyChanges {
                                target: loadResourcesRow
                                y: hammerLabel.y + 80
                                x: 0
                            }
                        },
                        State {
                            name: "EXPANDED"; when: textArea.text
                            ParentChange {
                                target: textArea
                                parent: addResourceFlickable.contentItem
                                x: 0; y: 0
                                width: window.width
                                height: window.height - buttonsRow.height
                            }
                        }
                    ]
                    state: "MINIMAL"
    
                    transitions: Transition {
                        ParentAnimation {
                            NumberAnimation { properties: "x,y,width,height";  easing.type: Easing.InCubic; duration: 600 }
                        }
                    }
    
                    // ensure focus remains when the area is reparented
                    onParentChanged: { textArea.forceActiveFocus(); }
                }
            }
    
            Text {
                id: experimentalText
    
                anchors {
                    horizontalCenter: parent.horizontalCenter
                    verticalCenter: parent.verticalCenter
                }
                text: qsTr("Experimental")
                enabled: false
                z: 0
                rotation: -45
                opacity: 0.1
                font.pixelSize: 96
            }
        }
    
        RowLayout {
            id: buttonsRow
    
            x: 0
            y: parent.height - height
            width: window.width
    
            Button {
                id: settingsButton
    
                text: "☰"
    
                onClicked: {
                    addResourcesPage.state = "EDITING_SETTINGS"
                }
            }
    
            Button {
                id: actionButton
    
                text: "stuff"
                visible: textArea.text || addResourcesPage.state === "EDITING_SETTINGS"
                Layout.fillWidth: true
            }
        }
    
    


  • Thanks so much! It does size back down normally now. I did miss setting the Flickable's height.

    How can I get the Flickable to scroll my TextArea's content though, instead of scrolling the whole TextArea itself?

    alt text



  • Any ideas on this? Still not able to solve the original problem - I've tried just about everything :(


Log in to reply