Search code examples
textqmlword-wrap

How to wrap some text in a rectangle in QML?


I have to perform a very simple task: I want to display a piece of text inside a rectangle and the size of that rectangle should precisely be the width of the text.

In C++, it's fairly easy to do. Just define the QString and apply the QFontMetrics to get its width. Then define the rectangle graphics element to have that size. It's done within five minutes.

I have heard that QML is easier to use. Therefore, I was expecting to solve that problem in less than five minutes. I didn't, and I'm still stuck at it. Here's what I have tried:

Rectangle {
    width: myText.contentWidth
    height: myText.contentHeight

    Text {
        anchors.fill:parent
        id: myText
        font.family: "Helvetica"
        font.pointSize: 50
        text:  qsTr("The string I want to display")
    }
}

This doesn't work for some reason I don't understand. I have found a way to do it in a way that doesn't exactly suits my needs:

Rectangle {
    width: 100
    height: 100

    MouseArea {
       id: myMouseArea
       anchors.fill: parent
       onClicked: parent.width=myText.contentWidth 
       hoverEnabled: true
     }

    Text {
        anchors.fill:parent
        id: myText
        font.family: "Helvetica"
        font.pointSize: 50
        text:  qsTr("The string I want to display")
    }
}

In this case, when I click the rectangle, it gets the correct width. Nevertheless, I am not interested in this solution, because I don't want to have to click to get a rectangle with the correct size.

I want that the rectangle's size gets the correct size whenever myText changes text. The use of onTextChanged in the Text item doesn't work either.

What am I missing here?


Solution

  • As far as I know, Font metrics were made available to developers in Qt 5.4, so they are relatively new, in QML. You got mainly FontMetrics and TextMetrics. A simple usage example:

    import QtQuick 2.4
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 280; height: 150
    
        TextMetrics {
            id: textMetrics
            font.family: "Arial"
            font.pixelSize: 50
            text: "Hello World"
        }
    
        Rectangle {
            width: textMetrics.width
            height: textMetrics.height
            color: "steelblue"
    
            Text {
                text: textMetrics.text
                font: textMetrics.font
            }
        }
    }
    

    As noted by Phrogz in the comment below, the TextMetrics type does not support measuring wrapped text.


    EDIT

    For what is worth I've never ever had the need to use metrics in QML. For me content* or painted* properties served the purpose and, as of Qt 5.12, they seem to work fine. Aka the following two solutions generate the correct visual behaviour:

    // solution 1
    Rectangle {
        width: myText.contentWidth
        height: myText.contentHeight
    
        Text {
            anchors.fill:parent
            id: myText
            font.family: "Helvetica"
            font.pointSize: 50
            text:  qsTr("The string I want to display")
        }
    }
    
    // solution 2
    Rectangle {
        width: myText.paintedWidth
        height: myText.paintedHeight
    
        Text {
            anchors.fill:parent
            id: myText
            font.family: "Helvetica"
            font.pointSize: 50
            text:  qsTr("The string I want to display")
        }
    }
    

    I would prefer those solutions to the usage of metrics for such a simple use case as the one proposed by the OP. For the opposite case - fitting a text in a specific size - a combination of properties can do the trick, e.g.:

    Rectangle {
        anchors.centerIn: parent
        width: 200
        height: 30
    
        Text {
            anchors.fill: parent
            text: "Wonderful Text"
            minimumPixelSize: 2
            fontSizeMode: Text.Fit
            font.pixelSize: 200
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
        }
    }
    

    Here the pixel size is simply over the top but the text still fits because a minimum size of 2 is set and the text has a clear fitting policy and clear boundaries, defined by the anchoring.