Problems launching a QProgressDialog after main window is made visible
-
Hi all,
I'm developing a desktop (Windows 10) application that needs to perform some very slow initilization (including file search in all local units). For this reason, I want to issue a QProgressDialog just after the main window is visible to inform the user about the progress of the initialization.
I've been loooking in forums for several days, and this problem seemed to be solved in up to four ways, but I can't it get it with any of the four methods.
So far, I'm getting my best results by overloading showEvent, and launching my initialization function (just once by using a control variable) inside showEvent after the main window is visible (see option 1 in code below). However, the result is unsatisfactory because, although I can see both the main window and the QProgressDialog, this does not show any motion in the progress bar until a (fake) QMessageBox is issued after the initialization process (please see the pictures below). If the message were not issued, the QProgressDialog would never appear.
By the way, is it possible to change the aspect of the bar in the QProgressDialog to make its text invisible, and so the bar will expand to the width of the dialog (just like in Qt Design editor, where setting "textVisible" = false gets this effect)? Or this can be made only by customizing the bar with "setBar"?
With the other three options proposed in forums I get absolutely worse results, including a Debug error. So, I must be doing something wrong, but I can't guess what. Can anyone teel me what I'm doing wrong, please?
I attach all the code (MainWindow.h and MainWindow.cpp; I don't think MainWindow.ui is necessary) in case someone may want to test. Note that all four options are implemented (although the three "worse" ones are commented) and can be tested.
MainWindow.h
#include <QDir> #include <QFile> #include <QIODevice> #include <QTextStream> #include <QProcess> #include <QStorageInfo> #include <QTimer> #include <windows.h> #include <QWidget> #include <QMessageBox> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT protected: void showEvent(QShowEvent *ev); // Options 1, 2 and 3: Comment signals declaration. // Option 4: Uncomment signal declaration. //signals: // void window_loaded(); public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_actionInitializeTestInit(); void on_actionExit_triggered(); void on_actionOptionMain1_triggered(); void on_actionOptionMain2_triggered(); void on_actionOptionMenu11_triggered(); void on_actionOptionMenu12_triggered(); void on_actionOptionMenu13_triggered(); void on_actionOptionMenu21_triggered(); void on_actionOptionMenu22_triggered(); private: QLabel* Label1; QLabel* Label2; bool TestInit_started; void CreateStatusBar(); void SearchFiles(const QString &start_path, QStringList &File_list); Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
MainWindow.cpp
#include <QtGlobal> #include "MainWindow.h" #include "ui_MainWindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); CreateStatusBar(); TestInit_started = false; // Options 1, 2 and 3: Comment "connect" // Option 4: Uncomment "connect" to connect my initialization function to // a signal which will be issued after showing MainWindow // connect(this, SIGNAL(MainWindow::window_loaded()), this, SLOT(on_actionInitializeTestInit()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // Initialize some internal elements QApplication::restoreOverrideCursor(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::showEvent(QShowEvent *ev) { QMainWindow::showEvent(ev); if (!TestInit_started) // Option 1: Comment "QMetaObject", "on_action..." and "emit", and // uncomment "QTimer" QTimer::singleShot(0, this, SLOT(on_actionInitializeTestInit())); // Option 2: Comment "QTimer", "on_action..." and "emit", and // uncomment "QMetaObject" // RESULT: Debugging error: Dead lock detected in BlockingQueuedConnection: Receiver is MainWindow(0x78fd00) // QMetaObject::invokeMethod(this, "on_actionInitializeTestInit", Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection)); // Option 3: Comment "QTimer", "QMetaObject" and "emit", and uncomment // "on_action..." // on_actionInitializeTestInit(); // Option 4: Comment "QTimer", "QMetaObject" and "on_action...", and // uncomment "emit" to issue signal "window_loaded" // emit MainWindow::window_loaded(); } void MainWindow::on_actionInitializeTestInit() { if (TestInit_started) // Yes, this check is written twice. Just in case... return; TestInit_started = true; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // Launch progress dialog. It's configured as a busy indicator because the // task to perform next is very slow. By the way, is it possible to change // the aspect of the bar to make its text invisible (just like in the // Design editor, where setting "textVisible" = false gets), and so the bar // will expand to the width of the dialog? Or that can be made only by // customizing the bar with "setBar"? // Regarding the progress dialog refresh, I've tried with "show", "repaint" // "update" and "processEvents". If I don't "show", the progress dialog // doesn't appear; if I don't "processEvents", the progres bar in the // progress dialog doesn't appear. So, this is my best combination so far. QProgressDialog progress(this); progress.setWindowModality(Qt::WindowModal); progress.setMinimumWidth(350); progress.setWindowTitle("Initializing TestInit"); progress.setLabelText("Searching files. This operation may last some minutes. Please wait..."); progress.setCancelButton(nullptr); progress.setRange(0, 0); progress.setAutoClose(true); progress.setValue(1); progress.show(); // progress.repaint(); // progress.update(); qApp->processEvents(); // First initialization step: Search in the whole system important files // for the application. This is a very slow operation, and that's why // the progress dialog is used as a busy indicator. // You can customize the file name to search in SearchFiles function below. QStringList File_list; foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes()) { if (storage.isValid() && storage.isReady() && !storage.isReadOnly() && ((storage.device().startsWith("\\\\?")) || (storage.device().startsWith("/dev/sda0")))) { QString rootpath(QDir::toNativeSeparators(storage.rootPath())); SearchFiles(rootpath, File_list); } } // This block is included just for entertainment. After the search, it // issues an info message showing the files found. // In the real application this message will not appear. The problem is // that the progress dialog does not appear until this message is issued. // Why? QApplication::restoreOverrideCursor(); QString allfiles; for (int i = 0; (i < File_list.size()); i++) { if (allfiles.size() > 0) allfiles += '\n'; allfiles += File_list.at(i); } QMessageBox::information(this, "Files found", allfiles, QMessageBox::Ok, QMessageBox::Ok); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // Other initializations are performed here. These are faster than the // previous one, and the progress dialog now behaves as usual. progress.setLabelText("Doing something else (1)."); progress.setRange(0, 1); progress.setAutoClose(true); progress.setValue(1); qApp->processEvents(); // Do something progress.setLabelText("Doing something else (2)."); progress.setValue(1); qApp->processEvents(); // Do something // And so on... progress.setValue(5); qApp->processEvents(); progress.close(); QApplication::restoreOverrideCursor(); } void MainWindow::on_actionExit_triggered() { close(); } void MainWindow::CreateStatusBar() { Label1 = new QLabel(this); Label1->setFrameStyle(QFrame::Panel | QFrame::Sunken); Label1->setAlignment(Qt::AlignRight); Label1->setMinimumSize(246, 20); Label1->setMaximumSize(246, 20); Label1->setIndent(2); Label1->setText(QString()); Label2 = new QLabel(this); Label2->setFrameStyle(QFrame::Panel | QFrame::Sunken); Label2->setAlignment(Qt::AlignRight); Label2->setMinimumSize(446, 20); Label2->setMaximumSize(446, 20); Label2->setIndent(2); Label2->setText(QString()); statusBar()->addPermanentWidget(Label1); statusBar()->addPermanentWidget(Label2); } void MainWindow::SearchFiles(const QString &start_path, QStringList &File_list) { QString targetfile("config.xml"); // Put here the filename you may want to test QString iparameters_fich = QDir::toNativeSeparators(start_path + '/' + targetfile); if (QFile(iparameters_fich).exists()) File_list << iparameters_fich; else { // Search configuration files in subdirectories QStringList nameFilter; QFileInfoList lstDirectoriesInfo = QDir(start_path).entryInfoList(nameFilter, QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); for (int i = 0; (i < lstDirectoriesInfo.size()); i++) SearchFiles(lstDirectoriesInfo.at(i).absoluteFilePath(), File_list); } } void MainWindow::on_actionOptionMain1_triggered() { // Do something } void MainWindow::on_actionOptionMain2_triggered() { // Do something } void MainWindow::on_actionOptionMenu11_triggered() { // Do something } void MainWindow::on_actionOptionMenu12_triggered() { // Do something } void MainWindow::on_actionOptionMenu13_triggered() { // Do something } void MainWindow::on_actionOptionMenu21_triggered() { // Do something } void MainWindow::on_actionOptionMenu22_triggered() { // Do something }
Thanks a lot in advance. Regards,
jcbaraza
-
@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
progress.setRange(0, 0); progress.setAutoClose(true); progress.setValue(1);
This doesn't make sense. You set the range from 0 to 0, then you set a value of 1.
Have you read the warning about modal QProgressDialogs?
Warning: If the progress dialog is modal (see QProgressDialog::QProgressDialog()), setValue() calls QApplication::processEvents(), so take care that this does not cause undesirable re-entrancy in your code.
From https://doc.qt.io/qt-5/qprogressdialog.html#value-prop
@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
is it possible to change the aspect of the bar in the QProgressDialog to make its text invisible
You mean this (
progressBar->setTextVisible(false)
)?@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
this does not show any motion in the progress bar
Check if your "slow tasks" are blocking your GUI thread. If they do so, it would cause the progressBar to wait until the tasks are done and continue afterwards.
-
Thank you for your suggestion about dialog modality. In my case, the QProgressDialog is merely an information window, with no control from the user (please note that there's no "Cancel" button). So, I don't think that modality may affect the operation of this particular dialog. Anyway, I've tried with both "WindowModal" and "NonModal" modes, and the result has been exactly the same in both cases.
And regarding the value, it works exactly the same setting value to 0, 1, or no setting any value (and regardless of the dialog modality).
Anyway, if I get more suggestions, I'll try with all possibilities, just in case...
Thanks again.
JCBaraza
-
@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
QTimer::singleShot(0, this, SLOT(on_actionInitializeTestInit()));
Are you using this timer just to fire your
InitializeTestInit()
function? I would use either the direct call instead of using a timer with 0 secs or emit your signal there.@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
connect(this, SIGNAL(MainWindow::window_loaded()), this, SLOT(on_actionInitializeTestInit()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
Does it work like this?
connect(this, SIGNAL(MainWindow::window_loaded()), .....
-> This part looks strange.
I think you are mixing old and new Signal & Slot syntax there. Remove theMainWindow::
part beforewindow_loaded()
or use the Qt5 syntax. -
I guess I've found the cause of your problem...
Documentation says:
setRange
: If the current value falls outside the new range, the progress dialog is reset with reset().
(from https://doc.qt.io/qt-5/qprogressdialog.html#setRange)This property holds whether the dialog gets hidden by reset()
The default is true.
(from https://doc.qt.io/qt-5/qprogressdialog.html#autoClose-prop)So I think you are resetting your ProgressDialog before anything can happen.
Another thing is, that you set static progress (
progress.setValue(1)
orprogress.setValue(5)
) which is probable not what you want (and your MAX Val is still at 1, when you setvalue
to 5, this will cause a reset as well). This is why you dont see any flow.To see the progressBar moving depending on your files left you could do something like this:
progress.setRange(0, maxFilesInFolder); for(int i = 0; i < maxFiles; i++){ progress.setValue(i); }
(It's hard to visualize the amount of time or files left, when using a
foreach
-loop to iterate through drives / directories because you never know what's going to come next (until you iterate twice, which isn't a good solution). -
@Pl45m4 said in Problems launching a QProgressDialog after main window is made visible:
I guess I've found the cause of your problem...
Documentation says:
setRange
: If the current value falls outside the new range, the progress dialog is reset with reset().
(from https://doc.qt.io/qt-5/qprogressdialog.html#setRange)This property holds whether the dialog gets hidden by reset()
The default is true.
(from https://doc.qt.io/qt-5/qprogressdialog.html#autoClose-prop)So I think you are resetting your ProgressDialog before anything can happen.
Another thing is, that you set static progress (
progress.setValue(1)
orprogress.setValue(5)
) which is probable not what you want (and your MAX Val is still at 1, when you setvalue
to 5, this will cause a reset as well). This is why you dont see any flow.To see the progressBar moving depending on your files left you could do something like this:
progress.setRange(0, maxFilesInFolder); for(int i = 0; i < maxFiles; i++){ progress.setValue(i); }
(It's hard to visualize the amount of time or files left, when using a
foreach
-loop to iterate through drives / directories because you never know what's going to come next (until you iterate twice, which isn't a good solution).Hi Pl45m4,
Thanks for all your proposals. I'll try to answer to all in this post.
The problem with such slow task is that it is impossible to know a priori the number of units and directories to dive in, and so I don't know what the range of the progress should be, and when to increment the value. For that reason, I've configured the QProgressDialog as a "busy indicator" (with the (0, 0) range; it's a trick that I found in a forum). This makes the progress bar to move continously indicating a background operation. So don't worry about the foreach loop ;-)
I've found a satisfactory solution for this problem: I've moved the "qApp->processEvents();" just before the foreach loop to the "SearchFiles" method (as the first sentence). And now it works very well, although the progress bar freezes for a while now and then, but the overall aspect is what I intended. Perhaps it can be improved, but this is an excellent restart point...
I think that this change is related to this comment from you:
Check if your "slow tasks" are blocking your GUI thread. If they do so, it would cause the progressBar to wait until the tasks are done and continue afterwards.
Regarding
Are you using this timer just to fire your InitializeTestInit() function? I would use either the direct call instead of using a timer with 0 secs or emit your signal there.
Yes, the QTimer was one of the four "good" proposals I found to launch a progress dialog just after making visible the main window (that included the direct call); and actually, it's the one that better worked for me. At least it was before moving the "qApp->processEvents();". Just in case, I'll try again with the new situation...
Does it work like this?
connect(this, SIGNAL(MainWindow::window_loaded()), ..... -> This part looks strange.
I think you are mixing old and new Signal & Slot syntax there. Remove the MainWindow:: part before window_loaded() or use the Qt5 syntax.My problem with the connect&slot method (in this case) is that I can't shot an existing signal indicating that the main window is already visible. So I invented my own "window_loaded" (declared in the header), but the debugger doesn't recognizes it. And about the syntax, I was sure I was using the old one, just llike in other parts of my application, where it works fine... Now that I'm not desperated anymore ( ;-) ), I can try again with the connect&slot.
And finally, tha stiil unsolved question
You mean this (progressBar->setTextVisible(false))?
Yes. If I could do something like that it would be wonderful, but I haven't found any property that does so.
Note that if you edit a QProgressBar in the Qt Design editor, you do have a property called "textVisible" that you can check or leave unchecked. If unchecked, the text indicating the percentage disappears, and the bar expands to its full size. Thst's what I want to do. I'm affraid the only way to customize the progress bar will be to replace that in the QProgressDialog (with setBar) with a customized one. I'll explore that option, although it's not critical.
Thank you so much for your interest and your comments.
Regards,
JCBaraza
-
Did you wrote something about the busy indicator? I must have missed it
Now it makes sense ;-)
But I've never heard about using the progressBar as busy indicator. How did the person from the other forum this "trick"?Yes I know that the window_loaded signal was made by you, but it should work even without the
MainWindow::
.@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
Note that if you edit a QProgressBar in the Qt Design editor, you do have a property called "textVisible" that you can check or leave unchecked
This is exactly what the line does ;) Or should do... Haven't tested it. Dunno if it shows the percentage value or just hides the text, but setting the text visibility to
true
orfalse
is pretty much the same as (un-)checking the property in Qt Designer.
(Everything that can be done with Designer, can be done with code only, but code is even more powerful)EDIT:
Just found an article and several posts on StackOverflow about this trick. The blog post said that all three properties need to have a value of zero (min, max and value, which is 1 or 5 in your case). Have you tried it without setting any (progress) value? -
@Pl45m4 said in Problems launching a QProgressDialog after main window is made visible:
Did you wrote something about the busy indicator? I must have missed it
Well, actually it was in the comments in the code, not in the post itself. Probably I cut it to reduce the size of the post. Sorry!
Now it makes sense ;-)
But I've never heard about using the progressBar as busy indicator. How did the person from the other forum this "trick"?Yes I know that the window_loaded signal was made by you, but it should work even without the
MainWindow::
.About this, I've moved the signal & connect to Qt5 format, and now it works ok (but I still must move the qApp->processEvents(); to SearchFiles function). Ah! The compiler forces me to write the two MainWindow::
connect(this, &MainWindow::window_loaded, this, &MainWindow::on_actionInitializeTestInit, Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
If I remove them, I get syntax errors.
@JCBaraza said in Problems launching a QProgressDialog after main window is made visible:
Note that if you edit a QProgressBar in the Qt Design editor, you do have a property called "textVisible" that you can check or leave unchecked
This is exactly what the line does ;) Or should do... Haven't tested it. Dunno if it shows the percentage value or just hides the text, but setting the text visibility to
true
orfalse
is pretty much the same as (un-)checking the property in Qt Designer.
(Everything that can be done with Designer, can be done with code only, but code is even more powerful)I'm affraid not in this case: there exist no member in QProgressDialog called setTextVisible (nor textVisible or similar); it only exists setVisible, which affects all the dialog...
I suppose it's because the setTextVisible is a member of QProgressBar. That's why I guess that the only way to get it in my QProgressDialog will be by customizing a QProgressBar and then replacing the bar in my QProgresDialog:
QProgressDialog progress(this); QProgressBar mybar; mybar.setTextVisible(false); progress.setBar(&mybar);
I've used this, and it works as I expected. And what's more. If you may need to change the progress display to progress mode instead of busy indicator, you only have to do is to change the text visibility of the progress bar and the progress dialog Range (and Value):
mybar.setTextVisible(true); progress.setRange(0, n); progress.setValue(i); // "i" means any value representing the percentage of the task performed // Continue operations varying Value as usual ... progress.setValue(n);
I hope this can be useful for someone.
EDIT:
Just found an article and several posts on StackOverflow about this trick. The blog post said that all three properties need to have a value of zero (min, max and value, which is 1 or 5 in your case). Have you tried it without setting any (progress) value?Actually I've tried setting Value to 0, 1 and not setting any value, and it works exactly the same in all cases, both when it worked and when it didn't. In fact, I've left in my example with Value unset, and it's working.
Thanks again for your interest.
Regards,