Номера строк/высота строки для Qml TextArea

Мы хотим реализовать встроенный редактор кода в нашем приложении на основе QtQuick. Для выделения мы используем QSyntaxHighlighter на основе KSyntaxHighlighting. Мы не нашли способа определить высоту строки и межстрочный интервал, которые позволили бы нам отображать номера строк рядом с кодом. Поддержка динамического переноса строк также была бы отличным дополнением.

    Flickable {
            id: flickable
            flickableDirection: Flickable.VerticalFlick
            Layout.preferredWidth: parent.width
            Layout.maximumWidth: parent.width
            Layout.minimumHeight: 200
            Layout.fillHeight: true
            Layout.fillWidth: true

            boundsBehavior: Flickable.StopAtBounds
            clip: true
            ScrollBar.vertical: ScrollBar {
                width: 15
                active: true
                policy: ScrollBar.AlwaysOn
            }

            property int rowHeight: textArea.font.pixelSize+3
            property int marginsTop: 10
            property int marginsLeft: 4
            property int lineCountWidth: 40

            Column {
                id: lineNumbers
                anchors.left: parent.left
                anchors.leftMargin: flickable.marginsLeft
                anchors.topMargin:   flickable.marginsTop
                y:  flickable.marginsTop
                width: flickable.lineCountWidth

                function range(start, end) {
                    var rangeArray = new Array(end-start);
                    for(var i = 0; i < rangeArray.length; i++){
                        rangeArray[i] = start+i;
                    }
                    return rangeArray;
                }

                Repeater {
                    model: textArea.lineCount
                    delegate:
                    Label {
                        color: (!visualization.urdfPreviewIsOK && (index+1) === visualization.urdfPreviewErrorLine) ? "white" :  "#666"
                        font: textArea.font
                        width: parent.width
                        horizontalAlignment: Text.AlignRight
                        verticalAlignment: Text.AlignVCenter
                        height: flickable.rowHeight
                        renderType: Text.NativeRendering
                        text: index+1
                        background: Rectangle {
                            color: (!visualization.urdfPreviewIsOK && (index+1) === visualization.urdfPreviewErrorLine) ? "red" : "white"
                        }
                    }
                }
            }
            Rectangle {
                y: 4
                height: parent.height
                anchors.left: parent.left
                anchors.leftMargin: flickable.lineCountWidth + flickable.marginsLeft
                width: 1
                color: "#ddd"
            }

        TextArea.flickable: TextArea {
                id: textArea

                property bool differentFromSavedState: fileManager.textDifferentFromSaved

                text: fileManager.textTmpState
                textFormat: Qt.PlainText
                //dont wrap to allow for easy line annotation wrapMode: TextArea.Wrap
                focus: false
                selectByMouse: true
                leftPadding: flickable.marginsLeft+flickable.lineCountWidth
                rightPadding: flickable.marginsLeft
                topPadding: flickable.marginsTop
                bottomPadding: flickable.marginsTop

                background: Rectangle {
                    color: "white"
                    border.color: "green"
                    border.width: 1.5
                }

                Component.onCompleted: {
                    fileManager.textEdit = textArea.textDocument
                }

                onTextChanged: {
                    fileManager.textTmpState = text
                }

                function update()
                {
                    text = fileManager.textTmpState
                }
            }
        }

Как видите, мы используем property int rowHeight: textArea.font.pixelSize+3 для определения высоты строки и межстрочного интервала, но это, конечно, прерывается, как только DPI или другие свойства системы изменяются.


person Simon Schmeißer    schedule 04.07.2019    source источник
comment
Взгляните на doc.qt.io/qt-5/qml- qtquick-fontmetrics.html. Вы также можете получить доступ к текстовому документу, чтобы выполнять более сложные действия: doc.qt.io/qt-5/qml-qtquick-textedit.html#textDocument-prop   -  person Mitch    schedule 05.07.2019


Ответы (3)


Тип TextArea имеет два свойства contentWidth и contentHeight, которые содержат размер текстового содержимого.

Итак, если вы разделите высоту на количество строк (которое вы можете получить с помощью свойства lineCount), вы получите высоту строки:

property int rowHeight: textArea.contentHeight / textArea.lineCount

Но если вы планируете иметь несколько межстрочных интервалов в одном и том же документе, вам придется обрабатывать каждую строку, манипулируя QTextDocument:

class LineManager: public QObject
{
    Q_OBJECT
    Q_PROPERTY(int lineCount READ lineCount NOTIFY lineCountChanged)
public:
    LineManager(): QObject(), document(nullptr)
    {}
    Q_INVOKABLE void setDocument(QQuickTextDocument* qdoc)
    {
        document = qdoc->textDocument();
        connect(document, &QTextDocument::blockCountChanged, this, &LineManager::lineCountChanged);
    }

    Q_INVOKABLE int lineCount() const
    {
        if (!document)
            return 0;
        return document->blockCount();
    }

    Q_INVOKABLE int height(int lineNumber) const
    {
        return int(document->documentLayout()->blockBoundingRect(document->findBlockByNumber(lineNumber)).height());
    }
signals:
    void lineCountChanged();
private:
    QTextDocument* document;
};
    LineManager* mgr = new LineManager();
    QQuickView *view = new QQuickView;
    view->rootContext()->setContextProperty("lineCounter", mgr);
    view->setSource(QUrl("qrc:/main.qml"));
    view->show();
Repeater {
    model: lineCounter.lineCount
    delegate:
        Label {
            color: "#666"
            font: textArea.font
            width: parent.width
            height: lineCounter.height(index)
            horizontalAlignment: Text.AlignRight
            verticalAlignment: Text.AlignVCenter
            renderType: Text.NativeRendering
            text: index+1
            background: Rectangle {
                border.color: "black"
            }
    }
}
person Dimitry Ernot    schedule 05.07.2019
comment
Вы случайно не знаете, как я буду обнаруживать (если включен) динамический перенос слов? Я предполагаю, что это где-то в QTextDocument, но когда я посмотрел туда, я не нашел ничего полезного (т.е. высота всегда была 0 и т. д.) - person Simon Schmeißer; 12.07.2019
comment
Вы хотите знать, включен ли перенос слов или текст был изменен для переноса? - person Dimitry Ernot; 12.07.2019
comment
хорошо, когда у меня включен перенос слов, эти строки с фактическим переносом слов должны иметь только один номер строки, верно? Итак, мне нужно как-то перебрать что-то и проверить каждую строку, является ли она полной или продолженной/обернутой? - person Simon Schmeißer; 15.07.2019
comment
Вы можете получить высоту каждой строки, манипулируя QTextDocument. Я обновлю свой ответ версией C++, чтобы справиться с этим. Я также попытаюсь воспроизвести это в QML. - person Dimitry Ernot; 15.07.2019

Я нашел решение только для QML:

  1. Используйте TextEdit вместо TextArea, чтобы избежать проблем с выравниванием между номерами строк и текстом.
  2. Используйте «ListView», чтобы сгенерировать номера строк для редактирования текста:

Вот исходное решение:

    RowLayout {
        anchors.fill: parent
        ListView {
            Layout.preferredWidth: 30
            Layout.fillHeight: true
            model: textEdit.text.split(/\n/g)
            delegate: Text { text: index + 1 }
        }
        TextEdit {
            id: textEdit
            Layout.fillWidth: true
            Layout.fillHeight: true
        }
    }

ListView имеет полную копию каждой строки текста. Мы можем использовать эту копию для вычисления высоты строки (с учетом переноса слов). Мы делаем это, создавая невидимый файл Text. Мы можем улучшить ответ, добавив Flickable к TextEdit и синхронизировав прокрутку между ListView и TextEdit:

Вот более полное решение:

// NumberedTextEdit.qml

import QtQuick 2.12
import QtQuick.Controls 2.5

Item {
    property alias lineNumberFont: lineNumbers.textMetrics.font
    property color lineNumberBackground: "#e0e0e0"
    property color lineNumberColor: "black"
    property alias font: textEdit.font
    property alias text: textEdit.text
    property color textBackground: "white"
    property color textColor: "black"

    Rectangle {
        anchors.fill: parent

        color: textBackground

        ListView {
            id: lineNumbers
            property TextMetrics textMetrics: TextMetrics { text: "99999"; font: textEdit.font }
            model: textEdit.text.split(/\n/g)
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.margins: 10
            width: textMetrics.boundingRect.width
            clip: true

            delegate: Rectangle {
                width: lineNumbers.width
                height: lineText.height
                color: lineNumberBackground
                Text {
                    id: lineNumber
                    anchors.horizontalCenter: parent.horizontalCenter
                    text: index + 1
                    color: lineNumberColor
                    font: textMetrics.font
                }

                Text {
                    id: lineText
                    width: flickable.width
                    text: modelData
                    font: textEdit.font
                    visible: false
                    wrapMode: Text.WordWrap
                }
            }
            onContentYChanged: {
                if (!moving) return
                flickable.contentY = contentY
            }
        }

        Item {
            anchors.left: lineNumbers.right
            anchors.right: parent.right
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.margins: 10

            Flickable {
                id: flickable
                anchors.fill: parent
                clip: true
                contentWidth: textEdit.width
                contentHeight: textEdit.height
                TextEdit {
                    id: textEdit
                    width: flickable.width
                    color: textColor
                    wrapMode: Text.WordWrap
                }
                onContentYChanged: {
                    if (lineNumbers.moving) return
                    lineNumbers.contentY = contentY
                }
            }
        }
    }
}
person Stephen Quan    schedule 02.08.2019

Я обнаружил, что вы можете запросить высоту строки с помощью FontMetrics, а затем получить истинную высоту с помощью Math.ceil(fontMetrics.lineSpacing), например:

TextEdit {
    id: textArea

     FontMetrics {
         id: fontMetricsId
         font: textArea.font
     }

     Component.onCompleted: {
          console.log("Line spacing:" + Math.ceil(fontMetricsId.lineSpacing)
     }
}
person vpicaver    schedule 16.05.2021