QTextEdit or QTextBrowser performance issue - qt

I have a heavy QString.
I need to display it as an output.
I tried both QTextEdit or QTextBrowser. And all methods of setting text like setText, append, setPlainText.....The performance is really poor. The most annoying things is that setting thing on user interface meaning blocking the main thread. So the program will become unresponsive during the process.
Is there any better way to display visual text result?

At least if the document is rich text, every time you append to the document, it is apparently reparsed.
This is A LOT more performant: If you want each append to actually show quickly & separately (instead of waiting until they've all been appended before they are shown), you need to access the internal QTextDocument:
void fastAppend(QString message,QTextEdit *editWidget)
{
const bool atBottom = editWidget->verticalScrollBar()->value() == editWidget->verticalScrollBar()->maximum();
QTextDocument* doc = editWidget->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(message);
cursor.endEditBlock();
//scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
//higher position)
if (atBottom) {
scrollLogToBottom(editWidget);
}
}
void scrollLogToBottom(QTextEdit *editWidget)
{
QScrollBar* bar = editWidget->verticalScrollBar();
bar->setValue(bar->maximum());
}
The scrolling to bottom is optional, but in logging use it's a reasonable default for UI behaviour.
This actually seems a kind of trap in Qt. I would know why there isn't a fastAppend method directly in QTextEdit? Or are there caveats to this solution?
(My company actually paid KDAB for this advice, but this seems so silly that I thought this should be more common knowledge.)
Original answer here.

Just got the same problem, and the resolution is very simple! Instead of creating a document + adding it immediately to the QTextBrowser/QTextEdit and then use cursor/set text to modify it, just postpone setting the document to the widget till AFTER you set the text / formatting... a complete life changer :)

The best way would be to load text partially as background operation with thread that periodically emit signal to redraw GUI, or better: just use QTimer.
Load first N lines, and then start QTimer that'll read more lines and append text inside widget. After reaching eof just kill that timer.
I believe that example can be helpful.

Related

How to realize a prompt dialog box when load a large file or other things using Qt?

I need to load a large file to parse and draw with OpenGL. The whole process is very time-consuming. So I want to realize a prompt dialog box before parse. But the code as following is not work.
void parseFile()
{
QMessageBox* msgbox = new QMessageBox();
msgbox->setModal(false);
msgbox->setWindowTitle(tr("message box"));
msgbox->setText("Please wait patiently......")
msgbox->show();
/* parse file and draw */
......
}
But it shows like also be frozen:
How to realize it?
show() does not actually show the content of the dialog. It only tells the event loop to show the dialog ASAP. But since you call more code after immediately after show(), the evnt loop does not have a chance to do its work.
The easiest way to solve this is to call QCoreApplication::processEvents() after msgbox->show(). This will force the event loop to do the work immediately.
If this does not work then try this parameter QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents)
Another option would be to move the heavy calculation to a function and than show the dialog and then schedule the calculation using a timer.
...
msgbox->show();
QTimer::singleShot(0, &doHeavyWork);
This would first handle all events related to showing the dialog and only after that it will start the heavy work (i.e. parsing the file).

Making changes to a QTextEdit without adding an undo command to the undo stack

I'm looking for a way to change the QTextCharFormat of a QTextEdit's QTextBlock without triggering the addition of an undo command. Let me explain:
The QTextCharFormat of a QTextBlock can be easily changed by using the QTextCursor::setBlockCharFormat() method. Assuming we have a QTextEdit called myTextEdit whose visible cursor is within the text block we want to change, we can change the textblock's QTextCharFormat like so:
text_cursor = myTextEdit.textCursor()
text_cursor.setBlockCharFormat(someNewCharFormat)
The above code works fine, but it will also add an undo command to the myTextEdit undo stack. For my own purposes, I would like to be able to change the QTextCharFormat of a QTextBlock without adding an undo command to the QTextEdit's undo stack.
I considered temporarily disabling the undo/redo system with the QTextDocument::setUndoRedoEnabled() method, but that method also clears the undo stack, which I don't want to do. I've also looked for other ways to change how the undo/redo system behaves, but I haven't found a way to get it to temporarily ignore changes. I simply want to make a change to a QTextEdit without the undo/redo system registering the change at all.
Any tips or suggestions are appreciated. Thanks!
You have to group this with previous modification. It is simple you have to surround code which does this modification with: beginEditBlock and endEditBlock. See documentation.
text_cursor = myTextEdit.textCursor()
text_cursor.beginEditBlock()
text_cursor.setCharFormat(someOtherCharFormat) # some previous modification
text_cursor.setBlockCharFormat(someNewCharFormat)
text_cursor.endEditBlock()
this way you will make a single commit for undo stack for any complex modification.
joinPreviousBlock() should do the trick:
cursor = self.textCursor()
cursor.joinPreviousEditBlock()
cursor.setPosition(start, QTextCursor.MoveAnchor)
cursor.setPosition(end, QTextCursor.KeepAnchor)
cursor.setCharFormat(fmt)
cursor.endEditBlock()
You should use QSyntaxHighlighter. Extends it and implement highlightBlock func, and call setFormat in it to change format without making undo/redo stack. See documentation for more detail.
If you feel QSyntaxHighlighter is not what you want, you can use QTextLayout. It is low level api and its setAdditionalFormats func doesn't make any undo stack.
range1 = QTextLayout.FormatRange()
range1.start = 0
range1.length = 10
range1.format = QTextCharFormat()
# additional ranges here...
textBlock.layout().setAdditionalFormats([range1, ...])
This is also used in the inside of QSyntaxHighlighter.

Reliably showing a "please wait" dialog while doing a lengthy blocking operation in the main Qt event loop

I've got a Qt app that needs to call an expensive non-Qt function (e.g. to unzip a ~200MB zip file), and since I'm calling that function from the main/GUI thread, the Qt GUI freezes up until the operation completes (i.e. sometimes for 5-10 seconds).
I know that one way to avoid that problem would be to call the expensive function from a separate thread, but since there isn't much the user can do until the unzip completes anyway, that seems like overkill. I can't add processEvents() calls into the expensive function itself, since that function is part of a non-Qt-aware codebase and I don't want to add a Qt dependency to it.
The only thing I want to change, then, is to have a little "Please wait" type message appear during the time that the GUI is blocked, so that the user doesn't think that his mouse click was ignored.
I currently do that like this:
BusySplashWidget * splash = new BusySplashWidget("Please wait…", this);
splash->show();
qApp->processEvents(); // make sure that the splash is actually visible at this point?
ReadGiantZipFile(); // this can take a long time to return
delete splash;
This works 95% of the time, but occasionally the splash widget doesn't appear, or it appears only as a grey rectangle and the "Please wait" text is not visible.
My question is, is there some other call besides qApp->processEvents() that I should also do to guarantee that the splash widget becomes fully visible before the lengthy operation commences? (I suppose I could call qApp->processEvents() over and over again for 100mS, or something, to convince Qt that I'm really serious about this, but I'd like to avoid voodoo-based programming if possible ;))
In case it matters, here is how I implemented my BusySplashWidget constructor:
BusySplashWidget :: BusySplashWidget(const QString & t, QWidget * parent) : QSplashScreen(parent)
{
const int margin = 5;
QFontMetrics fm = fontMetrics();
QRect r(0,0,margin+fm.width(t)+margin, margin+fm.ascent()+fm.descent()+1+margin);
QPixmap pm(r.width(), r.height());
pm.fill(white);
// these braces ensure that ~QPainter() executes before setPixmap()
{
QPainter p(&pm);
p.setPen(black);
p.drawText(r, Qt::AlignCenter, t);
p.drawRect(QRect(0,0,r.width()-1,r.height()-1));
}
setPixmap(pm);
}
Moving to another thread is the correct way to go but for simple operations, there's a much less complicated way to accomplish this without the pain of managing threads.
BusySplashWidget splash("Please wait…", this);
QFutureWatcher<void> watcher;
connect(&watcher, SIGNAL(finished()), &splash, SLOT(quit()));
QFuture<void> future = QtConcurrent::run(ReadGiantZipFile);
watcher.setFuture(future);
splash.exec(); // use exec() instead of show() to open the dialog modally
See the documentation about the QtConcurrent framework for more information.

Qt - signal for when QListWidget row is edited?

I am working in Qt4.7, and I have a QListWidget in my dialog. I have a QString that needs to match the current text in the row of this widget (the individual rows are editable). Looking at the signals associated with QListWidget, there seem to be signals for when a different index is selected but none for when the text of a the currently selected row changes. I thought currentTextChanged(QString) would do it, but it didn't. I also thought to try to connect each individual row to something, but QListWidgetItem doesn't have any built-in signals. Does anyone know of a way to do this? Thanks!
At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, changing colors, checking boxes, and anything else that "changes" the item! Predelnik pointed that out in his answer. Some people have tried to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!
I guess you need to look into the following signal:
void QListWidget::itemChanged(QListWidgetItem * item)
But be careful because it's being sent every time some property of item changed, not only text. I remember when we ran into the problem once when we changed item colors and got tons of false positive slots called because of that. If you need more fine tuning I guess it's better to write model/view classes yourself and not rely on QListWidget.

Flex - Is there a way to specify what direction a ComboBox will open?

Maybe I should further qualify this - Is there a way to specify which direction a ComboBox will open without copying and pasting the entire ComboBox class and ripping out the code where it determines which direction it will open in...
I'm my specific case - I need it to open upwards - always.
UPDATE: You can't fix this by subclassing it because the function that handles the direction of the opening is:
private function displayDropdown(show:Boolean, trigger:Event = null):void
And that bad boy uses a fair amount of private variables which my subclass wouldn't have access to...
If you build up the Menu object yourself, you can place the menu anywhere you want by simply setting the x,y coordinates of the menu object. You'll need to calculate those coordinates, but you might be able to do this easily without subclassing ComboBox.
I am doing something similar with PopUpButton; you might find it easier to work with PopUpButton. This is based on real code from my current project:
private function initMenu(): void {
var m:Menu = new Menu();
m.dataProvider = theMenuData;
m.addEventListener(MenuEvent.ITEM_CLICK, menuClick);
m.showRoot = false;
// m.x = ... <-- probably don't need to tweak this.
// m.y = ... <-- this is really the interesting one :-)
theMenu.popUp = m;
}
<mx:PopUpButton id="theMenu" creationComplete="initMenu()" ... />
BTW, to get the PopUpButton to act more like I wanted it (always popup, no matter where the click), setting openAlways=true in the MXML works like a charm.
I doubt it - you'd need to subclass the control (which isn't that big a deal.)
Maybe you could mess with the real estate so it's placed in such a fashion (e.g. crowded into the lower right corner) that up is naturally coerced?
I would recommend checking out this post. Yes, you do have to grab the ComboBox code and modify it, but at least now you have an idea where the modifications need to go.
You could set the MaxDropDownHeight, if you set it big enough Windows will automatically set the direction upwards.
This irritated me no end. I have uploaded a solution, its a simple Class that extends the PopUpButton and removes the logic of stage bounds detection as it failed 50% of the time anyway. My code just allows you to simply specify whether you want to open the menu up or down:
http://gist.github.com/505255

Resources