Refresh display of QTableWidget
-
Hi,
I have an apparently simple problem: consider the following code
QTableWidget* table=new QTableWidget(...); // initialisation of table QTableWidgetItem* it=NULL; QProcess p; int i; for( i=0; i<n; i++ ) { p.start('some external pgr called'); it=new QTableWidgetItem("text"); table->insertRow(i); table->setItem(i,0,it); table->update(); }
The problem I get is that 'table' will display the inserted rows only after the for loop is completed. How to force it to update at each pass inside the loop?
Thanks in advance,
jclaude
-
It's a wrong question to the problem. You don't want to "force" anything. What you want to do is insert an item into the table when a process starts, so focus on doing that. Also, you should use a separate QProcess object for each process you start. The way you use it is not designed to be "fire and forget".
Here's a sample that doesn't require "forcing" updates:
for (int i = 0; i<n; ++i) { auto p = new QProcess(); connect(p, qOverload<int>(&QProcess::finished), p, &QProcess::deleteLater); connect(p, &QProcess::started, [=] { auto it = new QTableWidgetItem("text"); table->insertRow(i); table->setItem(i, 0, it); }); p->start("some external pgr called"); }
You might also want to handle the
errorOccurred
signal of the process in some way (e.g. add a different item to the table). -
Thanks Chris Kawa for your answer. I could not try your suggestions because my version of Qt (5.6.1) apparently do not recognizes qOverload. I'll update it and try.
-
@jcga said in Refresh display of QTableWidget:
qOverload
I thinks its included in 5.7 to help using the new syntax.
You can just use the old syntax to test. -
qOverload
is just a syntax-shortener. You can use the more verbose version if it's not available to you:connect(p, static_cast<void(QProcess::*)(int)>(&QProcess::finished), p, &QProcess::deleteLater);
This ugliness is needed because there are two
finished
signals with different parameters and you need to help compiler choose the right one.
Hopefully in Qt 6 the overloaded signals will be gone and you'll be able to write justconnect(p, &QProcess::finished, p, &QProcess::deleteLater);
-
Or directly bypass the cast by explicitly specifying the template arguments, e.g.:
connect<void(QProcess::*)(int)>(p, &QProcess::finished, p, &QProcess::deleteLater);
Granted it's only marginally shorter, but is a bit more readable in my opinion.
-
@kshegunov To each his own, but without really knowing the template implementation of connect it isn't obvious what the template argument really refers to (at least here, where you've got the same class on sending and receiving end).
-
Fair enough. But that would be one of the really beautiful things about templates - having a convoluted implementation and terrible binary incompatible interface, wouldn't it ... no wonder the standards committee are pushing them templates so intently ... ;)
For the record the first template argument refers to the signal prototype, while the second refers to the slot prototype.
-
I tried the solution suggested by Chris Kawa. The program runs correctly, do what it has to do, but the QWidgetTable display is not updated during the for loop, but only after the loop finishes, as before. I think I implemented strictly the code as indicated by Chris, so I do not understand what happens.
-
The program runs correctly, do what it has to do, but the QWidgetTable display is not updated during the for loop, but only after the loop finishes, as before.
The code I posted wasn't suppose to update inside the loop, because I don't think it really needs to. Body of that loop is almost empty. Enforcing ui update after every iteration would just artificially slow it down. Usually you want to update ui when some state changes. In this case it's when a process is started, and yes, it happens after the execution leaves your code and gets back to the Qt's event loop.
There is a way to use your original code and "force" an update, but that's imho a bad design and I won't suggest how to do that unless you sign the fine print saying "I hereby want to willingly shoot myself in the foot".
-
@Chris-Kawa said in Refresh display of QTableWidget:
There is a way to use your original code and "force" an update, but that's imho a bad design and I won't suggest how to do that unless you sign the fine print saying "I hereby want to willingly shoot myself in the foot".
the way is to add
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
inside the loop but I agree is not the best design -
Thanks once more for your answers. Let me be a little bit more specific on what I'a trying to do, and why. As indicated in the sample code I provided, my program calls inside a for loop an external program. This exeternal program essentially reads data from a device, format them and save them to files, on file per loop execution. The tablewidget displays information about the file and the data saved. As reading data from the device can be relatively long (tens of seconds to few minutes), I would like the user to have some feedback on the advancement of the process by seeing the rows display progressively on the table. Thats is why I would like that added table rows become visible to the user during the execution of the loop, not only after. Anyway I also tried to add to my widget a QLabel playing the role of a status bar to indicate what the program is currently doing and I have exaclty the same problem, the QLabel do not updated its text inside the loop, but only after the loop is complete.
I'll try the solution proposed by VRonin, evec if not "best design", but I'm affraid it can have some undesirable side effects.
-
I wish VRonin haven't suggested that because that's the easy but messy way out. It leads to poor code that doesn't scale. Soon you'll see problems like something that you wouldn' t want to happen during the loop happens because it is processed when some signal fires etc. You'll design locks and flags around the problem, they will keep interfering with each other, you will add more bloat and the code will become unreadable mess. I've seen this all too many times. The problem is in the larger design of that piece of code.
You shouldn't do long operations in a loop in the ui thread. The loop should just start the tasks and exit. The tasks can take long time and they should signal you when they are started or done.
If the tasks are external programs then it's easy. Just start all the processes and connect tostarted
/finished
signals to update your ui to inform the user. If the tasks are some code in your own app that takes time to execute start them in separate threads and signal when they're done (QtConcurrent might be of use to you). There's also a tasking solution - you put tasks into a queue in a loop and resume execution and worker threads pick up the tasks from the queue and signal their progress/state. -
So I have tested VRonin's suggestions. It works, the program reacts as I wanted it to, and for the moment I have not noted any undesirable side-effect, thanks a lot. I would like to tray Chris' suggestion in his last message, but I don't master multithreading programming, so I have not understood how to implement it. If Chris has some time to spent indicating roughly the way to do it (specially, what function to call, or where to start reading the doc), I can try to find the details in the documentation myself.
jclaude