qtqmlqtquick2qquickitem

QML Flickable with TextArea: property binding for contentY is overwritten - by whom?


I'm making a terminal widget. I want the Flickable to scroll down to the latest input when TextArea.text is updated. My code looks like as follows.

ColumnLayout {
    anchors.fill: parent
    Flickable {
        id: scroller
        clip: true
        contentY: contentHeight - height
        onContentYChanged: {
            console.log("contentY:", contentY)
        }
        TextArea.flickable: TextArea {
            id: textArea
            Layout.fillWidth: true
        }
        Layout.fillWidth: true
        Layout.fillHeight: true
    }
    RowLayout {
        id: prompt
        Label {
            text: " > $ "
        }
        TextField {
            id: textInput
            Layout.fillWidth: true
        }
    }
}

When I run this, I see that contentY is being overwritten right after it's set by my binding:

qml: contentY: 1498
qml: contentY: 0
qml: contentY: 1517
qml: contentY: 0

I checked to make sure my binding is not setting it to 0. I tried to debug the binding loop using export QT_LOGGING_RULES="qt.qml.binding.removal.info=true", that came out clean. I had a look at the source for Flickable and I don't think any of the methods there are the culprit.

Is binding contentY the right way to do what I want? Why is my binding is not being respected?


Solution

  • The issue is that contentY is going to be overwritten constantly by the Flickable as the content inside of it moves around. You can't place a binding on it because as you can see, it will be immediately overwritten with a static value that updates as the user interacts with the flickable.

    What you need to do instead is something like

    onContentHeightChanged: Qt.callLater(() => contentY = contentHeight - height)
    

    Now, whenever the text area grows, it will jump the contentY via an immediate assignment instead of trying to rely on a binding. The callLater ensures that it happens after the flickable resets contentY to 0 on its own due to the height change.