Kiwi
Kiwi

Reputation: 235

How to make a TextArea have a max size and a scroll bar?

I have a TextArea that will usually only contain about one line of text. I do, however, want the user to be able to add more than that and have the TextArea expand up to a maximum of about 15 lines, at which point any additional text can be accessed via a scroll bar. I've been able to get the scrolling aspect working by having the TextArea be contained in a Flickable (which is ultimately contained in a Rectangle).

Rectangle {
    id: rec
    width: 200
    height: 25

    Flickable {
        id: flickable
        anchors.fill: parent
        contentWidth: textArea.width
        contentHeight: textArea.height

        TextArea.flickable:
            TextArea {
            id: textArea
            text: qsTr("Hello, world!")
            wrapMode: Text.WordWrap
        }
        ScrollBar.vertical: ScrollBar { }
    }
}

At this point, how would I go about having the text box expand with the text until some pre-defined max number of pixels (say, 300)?

Edit

Alright, almost there, just having one problem with getting the text to center properly with Mitch's solution. My main.qml file contains the following:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    HelloWorld{
        anchors.centerIn: parent
    }
}

Any my HelloWorld.qml file contains the following:

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.4

ColumnLayout{

    width: 250

    FontMetrics {
        id: fontMetrics
        font: textArea.font
    }

    Flickable {
        id: flickable
        width: parent.width
        height: Math.min(contentHeight, fontMetrics.height * 15)
        contentWidth: width
        contentHeight: textArea.implicitHeight
        clip: true

        TextArea.flickable: TextArea {
            id: textArea
            text: "Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! "
                + "Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! "
            wrapMode: Text.WordWrap
            //padding: 0

            background: Rectangle {
                border.color: "blue"
            }
        }
        ScrollBar.vertical: ScrollBar {}
    }

}

This is very close to working, but for one reason or another, when I have this code outside of main.qml, the text gets shifted lower and to the right until the user selects it:

enter image description here

After selecting the text, it looks like this (which is what I want it to start at): enter image description here

Upvotes: 5

Views: 4091

Answers (2)

Mitch
Mitch

Reputation: 24406

Set the height of the Flickable to be contentHeight or 300 - whichever is smaller:

import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    width: 400
    height: 400
    color: "#444"
    visible: true

    Rectangle {
        anchors.fill: flickable
    }

    Flickable {
        id: flickable
        width: parent.width
        height: Math.min(contentHeight, 300)
        contentWidth: width
        contentHeight: textArea.implicitHeight

        TextArea.flickable: TextArea {
            id: textArea
            text: qsTr("Hello, world! Hello, world! Hello, world! Hello, world! ")
            wrapMode: Text.WordWrap
        }
        ScrollBar.vertical: ScrollBar {}
    }
}

textarea

If you don't want the Rectangle to be where it is (a sibling of Flickable), you can remove it, add

clip: true

to the Flickable and

background: Rectangle {}

to the TextArea.

Also, if you want to be a bit more precise, you can use FontMetrics to calculate the line height:

import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    width: 400
    height: 400
    color: "#444"
    visible: true

    FontMetrics {
        id: fontMetrics
        font: textArea.font
    }

    Flickable {
        id: flickable
        width: parent.width
        height: Math.min(contentHeight, fontMetrics.height * 15)
        contentWidth: width
        contentHeight: textArea.implicitHeight
        clip: true

        TextArea.flickable: TextArea {
            id: textArea
            text: "Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! "
                + "Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! "
            wrapMode: Text.WordWrap
            padding: 0

            background: Rectangle {}
        }
        ScrollBar.vertical: ScrollBar {}
    }
}

Upvotes: 10

Kiwi
Kiwi

Reputation: 235

Aha! After much searching, I finally found a handy property called paintedHeight that's part of TextArea. This will get the height of the text in your TextArea, so I used this to find that the size of each line is 13 and simply update the height of the base Rectangle whenever the text is edited, and simply capping it with an if statement. Here's my wacky solution:

Rectangle {
    property int paintedHeight: 13
    id: rec
    width: 200
    height: paintedHeight * 2

    Flickable {
        id: flickable
        anchors.fill: parent
        contentWidth: textArea.width
        contentHeight: textArea.height

        TextArea.flickable:
            TextArea {
            id: textArea
            wrapMode: Text.WordWrap

            property int maxNumberOfLines: 15
            onTextChanged: {
                rec.height = (lineCount * rec.paintedHeight) + rec.paintedHeight

                // cap the height so that the box stops expanding at maxNumberOfLines
                if(rec.height > rec.paintedHeight * (maxNumberOfLines + 1)){
                    rec.height = rec.paintedHeight * (maxNumberOfLines + 1)
                }
            }
        }
        ScrollBar.vertical: ScrollBar { }
    }
}

In case anyone else finds themselves in a similar situation, just execute console.log(paintedHeight) to get the height of the current text (and divide accordingly if you have more than one line) to get the height of your text, as it may be different than mine depending on the style and font you're using (I'm using the Fusion style). Then just modify the paintedHeight property in the Rectangle with your size and the maxNumberOfLines property in the TextArea with the maximum number of lines you want the text to have.

Upvotes: 1

Related Questions