QML TextArea performance with large data buffers
-
I'm using Qt 6.6 with QML and a C++ backend.
I have a TextArea in a Flickable. The text is bound to a string in my C++ backend. I'm finding that it's slow to update when the text buffer is large and the backend signals that the text has changed.
It's taking 5s to redraw the TextArea when I have a 9MB buffer with about 21000 lines of text in it.
I find that this time changes linearly with the size of the text buffer.
In this blog post: https://www.qt.io/blog/text-editing-improvements-in-qt-quick
it says that TextEdit has been updated to only render the text that's in the viewport. It seems like if that were the case, the render time would not be increasing linearly with the buffer size.Note: the blog post references TextEdit, not TextArea. I've tried both and found no difference in performance.
Is there any way to check whether I'm getting that behavior?
Any other advice for improving performance?Flickable { id: flickable property var textCursorPosition: 0 property var yBuffer: 10 anchors.fill: dataViewerTextContainer boundsMovement: Flickable.StopAtBounds clip: true focus: true ScrollBar.horizontal: ScrollBar { anchors.right: parent.right anchors.rightMargin: 15 // prevent scrollbars from overlapping policy: flickable.contentWidth > flickable.width ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff } ScrollBar.vertical: ScrollBar { anchors.bottom: parent.bottom anchors.bottomMargin: 15 // prevent scrollbars from overlapping policy: flickable.contentHeight > flickable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff } TextArea.flickable: TextArea { id: dataViewerText function positionViewAtBeginning() { dataViewerText.cursorPosition = 0; } function positionViewAtEnd() { dataViewerText.cursorPosition = dataViewerText.length - 1; } function updateCursorPosition() { var bottomVisibleArea = flickable.visibleArea.yPosition + flickable.height - flickable.yBuffer; var positionInRoot = flickable.mapToItem(dataViewerText, 0, bottomVisibleArea); flickable.textCursorPosition = dataViewerText.positionAt(positionInRoot.x, positionInRoot.y); } font.family: "Courier New" font.pixelSize: root.themes.dataFontSize padding: 4 readOnly: true onTextChanged: { if (holdScroll.position === 0) dataViewerText.positionViewAtEnd(); else dataViewerText.cursorPosition = flickable.textCursorPosition; } Connections { target: dataViewerTab onDataViewerTextChanged: { // This is here so that multiple calls to updateDataViewerText() // don't bog down the UI thread console.log("DataViewerText changed"); var startTime = new Date().getTime() Qt.callLater(function() { console.log("Updating DataViewerText"); dataViewerText.text = dataViewerTab.dataViewerText; }); Qt.callLater(function() { console.log("dataviewer text update completed in: " + (new Date().getTime() - startTime) + "ms"); }); } } }
-
Unfortunately I cannot answer your question as I still use Qt 5, but this is an interesting topic for me as I had seen the post about improvements and it was something I was hoping to be able to take advantage of when we upgrade to Qt 6.
In my application, I also need to display a large text buffer that is being continuously updated. It is showing a log that is written by a process launched by the application. This sometimes runs for a long time and the log can get very large.
Like you, what I found when using
TextArea
was that, beyond a certain point, updates became intolerable and the application ground to a halt. From what I remember when I debugged it, the bottleneck was caused by an internal recalculation of the layout of the entire text buffer and this began to dominate once the buffer became large.In the end, the only way I found to have a log view in my application was to use a
ListView
with a model that consisted of the lines of text from the log. This has the advantage thatListView
naturally only worries about the relatively small viewport that it is currently displaying. The downside is that I had to implement all of the multiline selection behaviour myself, which was relatively hard work and some aspects of the behaviour aren't quite right compared with a genuine text area component. -
It is possible to do this - it's just that you have to code a lot of the selection logic yourself. I store selection information in my model, so that each item in the model comprises a "text" role as well as roles to capture "selected information" for that line. In the ListView delegate, where I use a read-only TextInput, I use the information to set selected the appropriate parts of each line. You then have all the mouse logic to identify an active selection, possibly spanning multiple lines, and update the model.
In my case, I only implemented single selection - you can only select one block of text at a time - but in principle it could be extended to multi-selection.