【问题标题】:Line numbers/ line height for a Qml TextAreaQml TextArea 的行号/行高
【发布时间】:2019-11-15 10:01:59
【问题描述】:

我们希望在基于 QtQuick 的应用程序中实现嵌入式代码编辑器。为了突出显示,我们使用基于KSyntaxHighlightingQSyntaxHighlighter。我们没有办法确定行高和行距,这样我们就可以在代码旁边显示行号。支持动态换行也是一个很好的补充。

    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 或系统的其他属性发生变化,这当然会中断。

【问题讨论】:

标签: qt qml qtquick2 kde


【解决方案1】:

TextArea 类型有两个属性 contentWidthcontentHeight 包含文本内容的大小。

因此,如果将高度除以行数(可以通过属性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"
            }
    }
}

【讨论】:

  • 您是否知道我将如何检测(如果启用)动态自动换行?我认为它在QTextDocument 的某个地方,但是当我在那里查看时,我发现没有任何用处(即高度始终为 0 等)
  • 您想知道是否启用了自动换行或文本是否已被修改为自动换行?
  • 好吧,当我启用自动换行时,那些带有实际自动换行的行应该只有一个行号,对吧?所以我需要以某种方式循环一些东西并检查每一行是完整的还是连续/包装的?
  • 你可以通过QTextDocument获取每行的高度。我会用 C++ 版本更新我的答案来处理这个问题。我也会尝试在 QML 中重现它。
【解决方案2】:

我找到了一个仅限 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 来做到这一点。我们可以通过在TextEdit 中添加Flickable 来进一步改进答案,并在ListViewTextEdit 之间同步滚动:

这里有一个更完整的解决方案:

// 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
                }
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    我发现您可以使用FontMetrics 查询行高,然后通过Math.ceil(fontMetrics.lineSpacing) 获取真实高度,例如:

    TextEdit {
        id: textArea
    
         FontMetrics {
             id: fontMetricsId
             font: textArea.font
         }
    
         Component.onCompleted: {
              console.log("Line spacing:" + Math.ceil(fontMetricsId.lineSpacing)
         }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-30
      • 2023-03-15
      • 2012-09-18
      • 2012-01-15
      • 1970-01-01
      • 2020-11-16
      • 2013-07-02
      相关资源
      最近更新 更多