Stewart
Stewart

Reputation: 4982

QML ScrollView does not scroll with TextArea

I'm trying to make a widget that has an image at the bottom, and text that populates from bottom-to-top. Here are three ASCII-art examples of the widget in action:

                                                Text Line 3   ^
                                                Text Line 4   |
                          Text Line 1           Text Line 5   |
    Text Line 1           Text Line 2           Text Line 6   V

  | ----------- |       | ----------- |       | ----------- |
  | ----------- |       | ----------- |       | ----------- |
+-----------------+   +-----------------+   +-----------------+
|   printer.svg   |   |   printer.svg   |   |   printer.svg   |
+-----------------+   +-----------------+   +-----------------+

I'm having problems with the vertical spacing. It should be:

The objects needed are pretty clear:

ColumnLayout
 +-- ScrollView
 |    +-- TextArea
 +-- Image

But what combinations Layout alignments/heights/implicitHeights/contentHeights/anchors/position/policy can make this happen?

The closest I have gotten is below, and the commented-out stuff is what I've been brute-forcing.

import QtQuick 2.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5


ColumnLayout {
    width: parent.width
    height: parent.height
    
    anchors.top: parent.top
    anchors.bottom: parent.bottom
    anchors.left: parent.left
    anchors.right: parent.right

    ScrollView {
        Layout.alignment: Qt.AlignBottom
        Layout.maximumHeight: parent.height - rect.height
        TextArea {
            text: 
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eleifend non leo 
a iaculis. Nam at tortor mollis, iaculis justo vel, dictum sapien. Vivamus sed
feugiat tortor, nec tempor nisi. Orci varius natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus. Quisque vestibulum, eros id vehicula
eleifend, eros tellus iaculis lectus, sit amet molestie justo ex in nulla. 
Maecenas at ultrices velit. Vestibulum eu libero tortor. Morbi ipsum lorem, 
interdum sed aliquam quis, semper pretium ante. Duis et nibh ac tortor tincidunt
commodo. Vestibulum commodo nibh nisi, in lacinia lectus imperdiet vel. Class 
aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos 
himenaeos.

Aliquam erat volutpat. Praesent eget erat a nisi vulputate euismod. Donec in 
vulputate tellus, eget dapibus eros. Quisque nec lacus iaculis, venenatis ante 
quis, tincidunt ligula. Donec aliquet diam sit amet nisl sollicitudin, et varius 
arcu lobortis. Aliquam erat volutpat. Duis enim justo, fringilla egestas 
volutpat et, efficitur id erat. Duis et ullamcorper leo. Maecenas ornare orci 
purus, ac commodo sapien malesuada eget. In dapibus ex nec risus laoreet 
fringilla. Donec maximus elit in elit aliquam, eget imperdiet mauris congue. 
Nunc libero quam, fringilla et mauris sit amet, consectetur convallis augue. Sed
vitae hendrerit ex, ac sollicitudin nibh. Donec diam mi, placerat vitae 
venenatis vitae, lacinia at urna."
        }
    }

    Rectangle { // Image placeholder
        id: rect
        Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
        width: 300
        height: 100
        color: "red"
    }
}

In this case, I am using anchors to force the ColumnLayout to fill its container. Then, things like Layout.alignment: Qt.AlignBottom behave well. I found that if I don't specify ScrollView{ Layout.maximumHeight: }, then the scroll bar doesn't appear.

It works well in the online sandbox I'm playing in, but in practice it fails miserably That's because this widget is contained in a StackLayout. When I try to include my widget in the project, I get:

QML ColumnLayout: Detected anchors on an item that is managed by a layout. This is undefined behaviour; use Layout.alignment instead.

So I've tried combinations of these lines:

ColumnLayout {
    Layout.alignment: Qt.AlignBottom
    height: parent.height

But neither line had any effect. I think that's because the parent deduces its size from its children.

I'd prefer not to change the rest of the application, but if I reduce the rest of the application to parent components and properties, I get something that looks like this. Is it even possible for me to write a bottom-to-top widget in an application like this?

ApplicationWindow {
    readonly property int base_width:  1920
    readonly property int base_height:  1080
    id: mainApplication
    width: base_width*4/5
    height: base_height*4/5
    
    Rectangle {
        id: iosMain
        anchors.fill: parent

        StackLayout
        {
            id: stackLayout
            anchors.left: menuButtonsLayout.right
            anchors.right: iosMain.right
            anchors.top: iosMain.top
            anchors.bottom: iosMain.bottom

            Item {
                id: pageRoot

                Layout.fillWidth: false
                Layout.fillHeight: false
                transformOrigin: Item.TopLeft
                width: childrenRect.width
                height: childrenRect.height
    
                RowLayout {
                    x: Style.pageLeftMargin
                    y: Style.pageTopMargin

                    StackLayout {
                        id: stackLayout
                        width: parent.width
                        currentIndex: 0
                        Layout.fillHeight: true

                        MyWidget{} // <-- This is where my widget lives
                    }
                }
            }
        }
    }
}

Upvotes: 0

Views: 309

Answers (1)

iam_peter
iam_peter

Reputation: 3914

This is the solution I came up with, I hope it fits your needs. I basically wrapped the ScrollView in an Item in order to fill the hole upper ColumnLayout cell via the attached property Layout.fillHeight. Then I use the height of the Item to place the ScrollView inside of it. First it is placed at the bottom until it grows bigger than the Item.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Window {
    id: root
    width: 480
    height: 320
    visible: true
    color: "white"

    Timer {
        interval: 500
        running: true
        repeat: true
        onTriggered: {
            textArea.text += "\nTip Tip Tip " + Date().toString()
            // scroll to bottom when text was added
            scrollView.ScrollBar.vertical.position = 1.0 - scrollView.ScrollBar.vertical.size
        }
    }

    StackLayout {
        anchors.fill: parent
        currentIndex: 0

        ColumnLayout {
            Layout.fillWidth: true
            Layout.fillHeight: true

            Item {
                id: frame
                Layout.minimumWidth: 100
                Layout.maximumWidth: 800
                Layout.preferredWidth: image.paintedWidth
                Layout.alignment: Qt.AlignHCenter
                Layout.fillHeight: true

                ScrollView {
                    id: scrollView
                    anchors.bottom: parent.bottom
                    width: parent.width
                    height: frame.height < scrollView.contentHeight ? frame.height : scrollView.contentHeight

                    TextArea {
                        id: textArea
                        color: "black"
                        text: "Typewriter Tip Tip Tip"

                        background: Rectangle {
                            border.color: "black"
                        }
                    }
                }
            }

            Image {
                id: image
                Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
                Layout.fillWidth: true
                Layout.minimumHeight: 100
                Layout.maximumHeight: 400
                source: "https://picsum.photos/300/100"
                fillMode: Image.PreserveAspectFit
            }
        }
    }
}

enter image description here

Upvotes: 2

Related Questions