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

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.

Related

Forking with QT

using QtCreator to make a loftier interface to a sofware.
There is basically a set of buttons to tune and inputs, a start and stop job
buttons, my problem comes from an infinite loop that freezes the display so I came up with using fork() so that the loop have to compete with the main program instead of eating up the whole resources (no multithreading), but the program crashes spiting:
[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi-threaded client and XInitThreads has not
been called
[xcb] Aborting, sorry about that.
a.out: ../../src/xcb_io.c:274: poll_for_event: Assertion
`!xcb_xlib_threads_sequence_lost' failed.
the fonction calling the loop is called 'ON', 'OFF' is supposed to exit the forked process.
//in button 'ON' func
ps = fork();
if(getpid() == ps)
{
while(1)
{
strcpy(word, charset(minlength, maxlength, N));
ui->pass->setText(word);//operation on the display
....SNIP
}
}
//In button 'OFF' func
if(getpid() == ps)
exit(0);
I'm really asking what is the correct way of starting a while(1) and be able to break, exit, or return from it while not freezing the window using QT, thanks.
You crash probably here:
ui->pass->setText(word);//operation on the display
as in Qt, you can not change UI from non UI threads directly. Only from signals and slots mechanism.
The proper way to not freeze UI is obviously to compute lengthy operations in another thread.
You can achieve this in several ways:
One is by sub-classing QObject class to create 'worker object' which would perform all heavy operations. You create new QThread object that should live as long as you need your object. And use QObject::moveToThread method to move created object to new thread. To control your worker object you should send signals from object and call it's slots also via signal-slot mechanism. If you call them directly - they will be executed in caller thread (so do not perform stuff like worker->startHeavyJob(); in UI thread). Instead emit signal in UI (emit sigStartHeavyStuff();) and connect it to slot of your worker object (slotDoHeavyStuff();)
if you do not want to bother with this (if operation is pretty small)
- you can use QApplication::processEvents() to process events in UI event loop while going in your infinite while loop.
Another way is to use QtConcurrentRun framework to run function in separate thread which manages itself. Threads are taken from thread pool and are managed by Qt. This approach looks like something you want to do. Though you still will be able to access UI objects only through signals and slots.
I see one big issue in the presented code that is causing your freeze: You never let Qt process anything while you are in the loop. You need to allow Qt to run it's event loop. The easiest way is to use QApplication::processEvents() inside the loop.
I'm also not a fan of a while(1) loop for a couple of reasons. The first of which is that it can eat machine cycles waiting for things to happen. I doubt you really need to run the code as fast as possible, you could probably get away with some sleeping in your loop.
The other issue is that it is hard to break out. A cleaner approach would be something like this
void MyClass::on_pushButton_ON_clicked()
{
MyClass::done = false; // this is a class attribute
while (!MyClass::done) {
QApplication::processEvents();
//...
}
}
void MyClass::on_pushButton_OFF_clicked()
{
MyClass::done = true;
}

QTextEdit or QTextBrowser performance issue

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.

How to call plain function from exec()?

I have 2 classes: one maintains some loop (at leas for 2-3 minutes; and is inherited from QObject) and another shows up a progress dialog (inherited from QDialog).
I want to start the loop as soon as the dialog is shown. My first solution was:
int DialogClass::exec()
{
QTimer::singleShot(0, LoopClassPointer, SLOT(start()));
return __super::exec();
}
There is a problem with throwing exceptions from slots. so I considered a possibility to make public slot start() just a public function. But now I don't know how to make it works well. Things like this:
int DialogClass::exec()
{
LoopClassPointer->start();
QApplication::processEvents();
return __super::exec();
}
don't help. The dialog doesn't appears.
Is there a common approach to this kind of situations?
some details, according to questions:
I have to work with system with its own styles, so we have a common approach in creating any dialogs: to inherit them from stytle class, which is inherited from QDialog.
my 'LoopClassPointer' is an exported class from separate dll (there is no UI support in it).
I have a 'start' button in main app, which connected with a slot, which creates progress dialog and 'LoopClassPointer'. at the moment I send 'LoopClassPointer' instance in the dialog and don't whant to make significant changes in the architecture.
Take a look at QtDemo->Concurrent Programming->Run function
e.g. in Qt 4.8: http://qt-project.org/doc/qt-4.8/qtconcurrent-runfunction.html
In this situation, I recommend you separate the logic of the loop from the dialog. Gui elements should always be kept separate.
It's great that your worker class is derived from QObject because that means you can start it running on a separate thread: -
QThread* m_pWorkerThread = new QThread;
Worker* m_pWorkerObject = new Worker; // assuming this object runs the loop mentioned
// Qt 5 connect syntax
connect(m_pWorkerThread, &QThread::started, m_pWorkerObject, &WorkerObject::start);
connect(m_pWorkerThread, &QThread::finished, m_pWorkerThread, &QThread::deleteThis);
m_pWorkerObject->moveToThread(m_pWorkerThread);
m_pWorkerThread->start();
If you're not familiar with using QThread, then start by reading this.
The only other thing you require is to periodically send signals from your worker object with progress of its work and connect that to a slot in the dialog, which updates its display of the progress.

QMainWindow stops receiving QEvent::UpdateRequest when user opens menu or resizes window

MyWindow which inherits from QMainWindow. MyWindow contains a QGLWidget that displays an animation.
The problem is that the animation pauses whenever I open a menu or resize the window.
The animation is implemented by calling QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)) periodically, then calling redrawing each time the window receives the QEvent::UpdateRequest, like this:
bool MyWindow::event(QEvent *event)
{
qDebug() << event;
switch (event->type())
{
case QEvent::UpdateRequest:
render();
return true;
default:
return QMainWindow::event(event);
}
}
As seen from qDebug(), while a menu is open or the window is being resized, the window stops receiving update request events.
Is there a setting on QMainWindow/QWidget to make it continue to receive update request events? Or is there some better way to implement the animation?
Edit: I'm on Mac OS X.
This may be a Qt bug. I'll investigate.
Alas, you're way overcomplicating your code.
The postEvent should be simply replaced by this->update(). Behind the scenes it posts the event for you.
One can simply connect a QTimer instance's signal to widget, SLOT(update()). If you want to save on a QObject instance, use QBasicTimer and reimplement timerEvent as follows: void MyWidget::timerEvent(QTimerEvent* ev) { if (ev.timerId() == m_timer.timerId()) update(); }
There's no need to deal with event() reimplementation. Simply reimplement paintEvent() - that's what it's for.
Qt GUI updates are performing on MainThread. So slow gui response is reasonable, if you have many gui functionality does at same time. So generally, do not overload MaiThread with so many heavey function calls.
Probable solution to speed up your GUI response.
If PostEvent is called by your MainThread( if you are using timer from main gui thread ), instead move those to backend functionality in
a worker thread and postEvent once it has been done.
you call QCoreApplication::processEvents(), after your render(); function in MainThread.
This will help system to process all the other events that are in the event-loop before to continue
Please check, following link How to improve GUI response
Note: When creating and triggering the timer it will run within your thread by default, it wont start another thread.
Since I haven't heard any more from Kuba Ober about the possibility of this being a Qt bug, I went ahead and filed a bug report: https://bugreports.qt-project.org/browse/QTBUG-33382
I was able to partially work around the problem by calling the render() function more directly — that is, instead of sending an event, receiving the event, and having the event handler call the function. I accomplished this with a dispatch queue (but not the main dispatch queue, since that's tied to the default run loop so it has the same problem). However, working with the QGLWidget on multiple threads was difficult. After trying for a while to use the moveToThread() function to make this work, and considering other factors involved in the project, I decided to use something other than Qt to display this window.

Force update of QTListWidget to add/remove elements

I'm attempting to wrap a GUI around an existing management console app. The main function is to search for network devices, which is given a timeout and is essentially a blocking call until the timeout has expired (using sleep to do the blocking). In this example the call is this->Manager->Search(...).
My issue is that I want the QListWidget to display "Searching..." while the search is taking place, and then update with the results at the completion of the search. My on-click code for the Search button is as follows:
void ManagerGUI::on_searchButton_clicked()
{
ui->IPList->clear();
new QListWidgetItem(tr("Searching..."), ui->IPList);
ui->IPList->repaint();
this->Manager->Search(static_cast<unsigned int>(this->ui->searchTime->value()*1000.0));
ui->IPList->clear();
if(this->Manager->GetNumInList() != 0)
this->displayFoundInList(this->Manager->GetFoundList());
else
new QListWidgetItem(tr("No Eyes Found"), ui->IPList);
ui->IPList->repaint();
}
When I hit the button, the QListWidget IPList does not update until after the timeout has taken place (and I assume until after this callback has terminated). Does anyone have any suggestions? I was under the impression that calling ui->IPList->repaint() would cause an immediate redraw of the list.
Additional info:
QT Version 5.1.0 32-Bit
Compiled using VS2012
Running on Win7 Pro 64-bit (but to be ported to OSX and Linux, so nothing win-specific please)
1) You don't need to call repaint directly.
2) You should do your search asynchronously. It is big topic - you should learn basics of Qt first.
Start with signals and slots and then learn about QThread or QtConcurrent. Then implement a class that will do searching and send necessary signals: first signal on search start, second signal - on search stop. Then connect slots to this signals and work with your list view insine this slots.
Problem is that your "Search manager" blocks Qt's event loop. Thats why listview does not repainted.
You need a signal slot system because your search is blocking. Ideally you should do the search in a new thread. However you can cheat with a processEvents()
void ManagerGUI::on_searchButton_clicked()
{
ui->IPList->clear();
new QListWidgetItem(tr("Searching..."), ui->IPList);
emit signalStartSearch();
}
void ManageGUI::slotStartSearch()
{
// Process any outstanding events (such as repainting)
QCoreApplication::processEvents();
this->Manager->Search(static_cast<unsigned int>(this->ui->searchTime->value()*1000.0));
emit signalSearchCompleted();
}
void ManagerGUI::slotSeachCompleted()
{
ui->IPList->clear();
if(this->Manager->GetNumInList() != 0) {
ui->IPList->setUpdatesEnabled(false);
this->displayFoundInList(this->Manager->GetFoundList());
ui->IPList->setUpdatesEnabled(true);
} else {
new QListWidgetItem(tr("No Eyes Found"), ui->IPList);
}
}
Ideally you would want the Manager->Search to emit the signal and then use QtConcurrent::run to do the search in another thread.

Resources