3

I want to set the text of a label on main window when a function in code is running. But the label does not change until the function ends.

for (int i = 0; i < files.size(); ++i) {

    ui->label->setText(files[i]);
    myfoo();

}
Nejat
  • 31,784
  • 12
  • 106
  • 138

3 Answers3

4

The reason the label isn't seen to update is that the main thread, which updates the GUI, is busy in your for loop and not getting time to process events which, amongst other things, causes the GUI widgets to be redrawn.

While you could use QApplication::processEvents, this is not ideal, especially if many events are received during the processing of your function.

The proper way to handle this is to create a separate thread (QThread) and an object, derived from QObject, which will do work while sending messages to the main (GUI) thread to update the label.

Rather than repeating the code, I suggest you read this article on how to use QThread properly. It's not that difficult.

Then you would change your function, which is now in the object on the 2nd thread, to something like this: -

for (int i = 0; i < files.size(); ++i) 
{
    // calling UpdateLabel signal, which connects to an object on the main thread
    UpdateLabel(files[i]);        
    myfoo();  
}

Assuming the signal from the Worker class has been connected to a slot in an object on the main thread, such as QMainWindow, you'd receive the text in the slot and update the label: -

void QMainWindow::UpdateLabel(const QString text)
{
    ui->label->setText(text);
}
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • Sorry, I don't success this way. My mainwindow has a slot "updateLabelText()".. And there is a block in my code. the block is : for() { QThread* thread = new QThread; // Worker* worker = new Worker(); this->moveToThread(thread); connect(thread, SIGNAL(started()), this, SLOT(updateLabelText())); thread->start(); } .... And the slot sets text of labels. I get an erro in runtime,it says that "moveToThread: Widgets cannot be moved to a new thread" –  Apr 03 '14 at 17:21
  • This link says that "You can't move widgets into another thread " http://stackoverflow.com/questions/2207527/changing-a-label-in-qt –  Apr 03 '14 at 17:23
  • That is correct, which is why my answer recommends creating a worker class derived from QObject and moving that to a different thread. – TheDarkKnight Apr 04 '14 at 07:16
  • Maybe I cannot understand you...My main class derived QObject. I create a new thread,and task of this thread write to label. But my compiler says you can't.. –  Apr 05 '14 at 20:59
  • What do you mean by "task of this thread write to label"? I'm assuming that English is not your first language; sorry, but it doesn't make sense, so can you please explain, preferably by editing the question and showing the code you're now using, along with the compiler error. – TheDarkKnight Apr 07 '14 at 08:05
  • Sorry,it is true that English is not my first language. I did another ui element for this.I use status bar and update it's value. –  Apr 07 '14 at 12:11
  • Are you still having problems? – TheDarkKnight Apr 07 '14 at 12:14
0

This happens because your loop is blocking the event loop - you never return to the event loop. When most any slot is called in your code, it is called from the event loop. The event loop can only proceed when you return from the slot, or from the QObject::event method.

With Qt 5 and C++11, it's very easy to update the file list from a separate thread.

QtConcurrent::run([this] {
  // This code runs in a separate thread
  for (int i = 0; i < files.size(); ++i) {
    // This is safe for cross-thread use
    QMetaObject::invokeMethod(ui->label, "setText", files[i]);
    myfoo();
  }
});

The implementation of myfoo() must be thread safe, and cannot access any widgets directly. It can still safely "write" to widgets by using the invokeMethod mechanism, as it is cross-thread.

A reasonably good starting point in demonstrating how to write fully multithreaded file access that doesn't block the gui is given in my photomosaic Qt example, implemented in a mere 300 lines.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
-1

You could execute your for-loop in a separate thread that only sends a signal to the gui thread, which then calls label->setText() in the appropriate slot. (Use Qt::QueuedConnection)

Or try this:

for (int i = 0; i < files.size(); ++i) {
    ui->label->setText(files[i]);
    QApplication::processEvents();
    myfoo();  
}
BBRodriguez
  • 306
  • 1
  • 4
  • `processEvents` reenters the event loop and can lead to effects that are just slightly less bad than unsynchronized access to data from multiple threads. – Kuba hasn't forgotten Monica Mar 28 '14 at 15:38
  • Which is why I mentioned the Threading-Solution first. But depending on what is done, processEvents COULD be the better (=simpler) solution – BBRodriguez Mar 28 '14 at 15:47
  • `processEvents` is essentially a hack that should be used in very measured circumstances. It's very dangerous, since suddenly *all* of your code has to be designed with reentrancy in mind, and you can't even use something as simple as a mutex to protect against it, since you'll just get a deadlock. `processEvents` should only be called from library code that was designed with reentrancy in mind. It does not belong in general-purpose implementation code. – Kuba hasn't forgotten Monica Mar 28 '14 at 15:56
  • Why should `processEvents` cause the need to design all your code with reentrancy in mind? It is all running in the same thread, so there won't be any concurrent accesses to data (Provided theres only one thread at all). Of course `processEvents` can lead to problems, especially regarding performance, why one should be careful using it. But there are situations where it is the better choice... (Because it keeps your code simple) – BBRodriguez Mar 28 '14 at 16:24
  • @MarioBlueSkies Re-entrancy doesn't need to have anything to do with threads. Even with one thread, if calling `processEvents` from a method, it may end up calling that method again, before returning. Therefore the method needs to be ok with being called like that: re-entrant. So any code which may call method/function which uses `processEvents` needs to be re-entrant, unless it's 100% sure event loop will not call it again. – hyde Mar 29 '14 at 19:35
  • @hyde That's a good point... this way, of course, it can be very dangerous. But there are enough situations where you can be sure. For example if you're calling it from a slot that is (only) connected to the clicked()-signal of a button. If you pass ExcludeUserInputEvents to processEvents, that would be a situation where it is safe to use... – BBRodriguez Mar 31 '14 at 14:26