Problems with stacked Progress Dialogs
-
Qt 6.9.0 on Windows11, with C++ in Visual Studio
I have a program that runs different search algorithms in sequence. I want to show a global progress indicator that shows the number of executed steps, and for each step a separate indicator that shows progress of execution of that particular algorithm. I included a small runnable program at the bottom that shows the setup.
These are the problems:
- The progress dialog window is initially completely blank for some time
- The step progress dialog is show before the global progress dialog
- The setMinimumDuration value seems to be ignored for the step progress dialogs
- Once a step dialog has been shown, it is not possible to cancel the global dialog anymore
Is something wrong in my setup? Thanks for your assistance!
#include "stdafx.h" #include <windows.h> #include <thread> #include <QtCore/QPoint> #include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow> #include <QtWidgets/QPushButton> #include <QtWidgets/QProgressDialog> QMainWindow* mainWindow; QProgressDialog* progressStep = nullptr; QProgressDialog* progressGlobal = nullptr; bool progressStepIsCanceled; bool progressGlobalIsCanceled; void progressStepCancel() { progressStepIsCanceled = true; } void progressGlobalCancel() { progressGlobalIsCanceled = true; } inline bool progressStepCanceled() { if( not progressStep ) return false; return progressStepIsCanceled or progressGlobalIsCanceled; } inline bool progressGlobalCanceled() { if( not progressGlobal ) return false; return progressGlobalIsCanceled; } inline void progressStepProgress( const int CurrentStep ) { if( not progressStep ) return; progressStep->setValue( CurrentStep ); } inline void progressGlobalProgress( const int CurrentStep ) { if( not progressGlobal ) return; progressGlobal->setValue( CurrentStep ); } void progressStepClose() { if( not progressStep ) return; progressStep->setValue( progressStep->maximum() ); progressStep->close(); progressStep = nullptr; } void progressGlobalClose() { // Also cancel the strategy progress window, if any progressStepClose(); if( not progressGlobal ) return; progressGlobal->setValue( progressGlobal->maximum() ); progressGlobal->close(); progressGlobal = nullptr; } void RunStep() { progressStep = new QProgressDialog( "Step progress", "Cancel", 0, 5, mainWindow ); progressStep->setWindowModality( Qt::WindowModal ); QPoint Qp{ mainWindow->pos() }; QPoint Incr( 100, 100 ); progressStep->move( Qp + Incr ); progressStep->setMinimumDuration( 1500 ); QObject::connect( progressStep, &QProgressDialog::canceled, mainWindow, &progressStepCancel ); progressStep->setValue( 0 ); progressStepIsCanceled = false; for( int si = 0; si <= 5; si++ ) { progressStepProgress( si ); std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); if( progressStepCanceled() ) break; } progressStepClose(); } void RunGlobal() { progressGlobal = new QProgressDialog( "Global progress", "Cancel", 0, 10, mainWindow ); progressGlobal->setWindowModality( Qt::WindowModal ); QPoint Qp{ mainWindow->pos() }; QPoint Incr( 50, 50 ); progressGlobal->move( Qp + Incr ); progressGlobal->setMinimumDuration( 500 ); QObject::connect( progressGlobal, &QProgressDialog::canceled, mainWindow, &progressGlobalCancel ); progressGlobal->setValue( 0 ); progressGlobalIsCanceled = false; std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); for( int gi = 0; gi <= 20; gi++ ) { progressGlobalProgress( gi ); std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); RunStep(); if( progressGlobalCanceled() ) break; } progressGlobalClose(); } int APIENTRY _tWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow ){ int argc = 0; char Argv[1][100]; char* argv = Argv[0]; QApplication* Application = new QApplication( argc, &argv ); mainWindow = new QMainWindow; QPushButton* Start = new QPushButton( mainWindow ); Start->setText( "Start" ); QObject::connect( Start, &QPushButton::clicked, mainWindow, &RunGlobal ); mainWindow->show(); Application->exec(); return 0; }
@JanLaloux said in Problems with stacked Progress Dialogs:
Is something wrong in my setup?
Is the code above just an example or your real code?
Why you usewinMain
? Qt already does that for you when you are on Windows and create a new Qt Widget App. -
What has winmain to do with QtCreator? Simply use main()
-
Qt 6.9.0 on Windows11, with C++ in Visual Studio
I have a program that runs different search algorithms in sequence. I want to show a global progress indicator that shows the number of executed steps, and for each step a separate indicator that shows progress of execution of that particular algorithm. I included a small runnable program at the bottom that shows the setup.
These are the problems:
- The progress dialog window is initially completely blank for some time
- The step progress dialog is show before the global progress dialog
- The setMinimumDuration value seems to be ignored for the step progress dialogs
- Once a step dialog has been shown, it is not possible to cancel the global dialog anymore
Is something wrong in my setup? Thanks for your assistance!
#include "stdafx.h" #include <windows.h> #include <thread> #include <QtCore/QPoint> #include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow> #include <QtWidgets/QPushButton> #include <QtWidgets/QProgressDialog> QMainWindow* mainWindow; QProgressDialog* progressStep = nullptr; QProgressDialog* progressGlobal = nullptr; bool progressStepIsCanceled; bool progressGlobalIsCanceled; void progressStepCancel() { progressStepIsCanceled = true; } void progressGlobalCancel() { progressGlobalIsCanceled = true; } inline bool progressStepCanceled() { if( not progressStep ) return false; return progressStepIsCanceled or progressGlobalIsCanceled; } inline bool progressGlobalCanceled() { if( not progressGlobal ) return false; return progressGlobalIsCanceled; } inline void progressStepProgress( const int CurrentStep ) { if( not progressStep ) return; progressStep->setValue( CurrentStep ); } inline void progressGlobalProgress( const int CurrentStep ) { if( not progressGlobal ) return; progressGlobal->setValue( CurrentStep ); } void progressStepClose() { if( not progressStep ) return; progressStep->setValue( progressStep->maximum() ); progressStep->close(); progressStep = nullptr; } void progressGlobalClose() { // Also cancel the strategy progress window, if any progressStepClose(); if( not progressGlobal ) return; progressGlobal->setValue( progressGlobal->maximum() ); progressGlobal->close(); progressGlobal = nullptr; } void RunStep() { progressStep = new QProgressDialog( "Step progress", "Cancel", 0, 5, mainWindow ); progressStep->setWindowModality( Qt::WindowModal ); QPoint Qp{ mainWindow->pos() }; QPoint Incr( 100, 100 ); progressStep->move( Qp + Incr ); progressStep->setMinimumDuration( 1500 ); QObject::connect( progressStep, &QProgressDialog::canceled, mainWindow, &progressStepCancel ); progressStep->setValue( 0 ); progressStepIsCanceled = false; for( int si = 0; si <= 5; si++ ) { progressStepProgress( si ); std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); if( progressStepCanceled() ) break; } progressStepClose(); } void RunGlobal() { progressGlobal = new QProgressDialog( "Global progress", "Cancel", 0, 10, mainWindow ); progressGlobal->setWindowModality( Qt::WindowModal ); QPoint Qp{ mainWindow->pos() }; QPoint Incr( 50, 50 ); progressGlobal->move( Qp + Incr ); progressGlobal->setMinimumDuration( 500 ); QObject::connect( progressGlobal, &QProgressDialog::canceled, mainWindow, &progressGlobalCancel ); progressGlobal->setValue( 0 ); progressGlobalIsCanceled = false; std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); for( int gi = 0; gi <= 20; gi++ ) { progressGlobalProgress( gi ); std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); RunStep(); if( progressGlobalCanceled() ) break; } progressGlobalClose(); } int APIENTRY _tWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow ){ int argc = 0; char Argv[1][100]; char* argv = Argv[0]; QApplication* Application = new QApplication( argc, &argv ); mainWindow = new QMainWindow; QPushButton* Start = new QPushButton( mainWindow ); Start->setText( "Start" ); QObject::connect( Start, &QPushButton::clicked, mainWindow, &RunGlobal ); mainWindow->show(); Application->exec(); return 0; }
@JanLaloux said in Problems with stacked Progress Dialogs:
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
I only glanced through the code (TL;DR), but this is a terrible thing to do in a GUI or other interactive program. The UI will be completely unresponsive to user input and graphic updates during the sleep period. Use a QTimer and a slot for the next step if the UI must wait for some period of time before continuing.
-
The sleep is just for the demo program of course, it replaces the processing that happens in the real program. I could write a for loop with some dummy calculations, but that does not change fundamentally the setup: a main loop that launches one step after the other in sequence, the main loop shows global progress (= number of steps launched), each step show it's progress too in a separate progress window, and it must be possible to stop a step (and continue with the rest) or stop globally.
-
The sleep is just for the demo program of course, it replaces the processing that happens in the real program. I could write a for loop with some dummy calculations, but that does not change fundamentally the setup: a main loop that launches one step after the other in sequence, the main loop shows global progress (= number of steps launched), each step show it's progress too in a separate progress window, and it must be possible to stop a step (and continue with the rest) or stop globally.
@JanLaloux said in Problems with stacked Progress Dialogs:
it replaces the processing that happens in the real program
This does not change anything.
As long as you're doing long lasting processing in UI thread you're blocking the user interface.
Move this processing to other threads. -
What has winmain to do with QtCreator? Simply use main()
@Christian-Ehrlicher: This is VisualStudio, depending on the type of project you choose you will get another main statement. If you change it into a simple "main()" you will get a linking error that "WinMain" is missing.
-
@JanLaloux said in Problems with stacked Progress Dialogs:
it replaces the processing that happens in the real program
This does not change anything.
As long as you're doing long lasting processing in UI thread you're blocking the user interface.
Move this processing to other threads.@jsulm I don't agree. If there is only the global progress window (no step progress shown), the progress dialog remains responsive and it can be cancelled at any time, no other thread is needed. You have of course to check regularly if the cancel button was pressed, and the QProgressDialog::canceled signal is connected to a slot for this.
Also, a step progress dialog is reactive as it should, no running in another thread needed. It is only when two progress dialogs are created and running at the same time that the problems arise -
@Christian-Ehrlicher: This is VisualStudio, depending on the type of project you choose you will get another main statement. If you change it into a simple "main()" you will get a linking error that "WinMain" is missing.
@JanLaloux said in Problems with stacked Progress Dialogs:
This is VisualStudio, depending on the type of project you choose you will get another main statement. If you change it into a simple "main()" you will get a linking error that "WinMain" is missing.
I don't agree ;-)
You don't have to usewinMain
manually for Qt apps. Not in Qt Creator, not in Visual Studio.Sure, if you pick
Console Application
, you can't get rid of that. -
@JanLaloux said in Problems with stacked Progress Dialogs:
Is something wrong in my setup?
Is the code above just an example or your real code?
Why you usewinMain
? Qt already does that for you when you are on Windows and create a new Qt Widget App.@Pl45m4 said in Problems with stacked Progress Dialogs:
Is the code above just an example or your real code?
I guess this question is asked because your source makes most of us squeam in horror. Others have already pointed out that you should use regular
main()
for Qt programs. It doesn't help that you are creating your ownargc
andargv
variables to be passed to the QApplication constructor.It is really bad practice to use global variables. Everything above your main function should go into a
MainWindow
class derived fromQMainWindow
.You should also avoid using pointers whereever possible. In your particular case there is no reason for
Application
andmainWindow
to be pointers (butmainWindow
should also be a local variable). Theoretically, you are leaking memory. (Practically, it doesn't matter because the OS will clean up after you.)@JanLaloux said in Problems with stacked Progress Dialogs:
I don't agree. If there is only the global progress window (no step progress shown), the progress dialog remains responsive and it can be cancelled at any time, no other thread is needed. You have of course to check regularly if the cancel button was pressed, and the QProgressDialog::canceled signal is connected to a slot for this.
My experience is totally different. I have never seen a smooth animation of the progress bar when doing it this way. And you also have to hit the cancel button at the right time to be able to cancel progress. This is the reason why you'll find many discussions in this forum using QApplication::processEvents to make the progress dialog somehow work properly. But, everybody will tell you not to use QApplication::processEvents. Your actual computation will take way longer if you do this. The only proper way to do this is actually to use a separate thread. I have simplified this for myself and put it into a library: https://github.com/SimonSchroeder/QtThreadHelper. Just use
workerThread([](){ /* do stuff */ });
to run any function in another thread (this is derived from QThread::create followed by start()). If you want to wait for the work to finish, useworkerThreadJoin()
instead. There's another helperguiThread()
which I typically use to update the progress bar. You'll find an example in the readme of my lib. -
@Pl45m4 said in Problems with stacked Progress Dialogs:
Is the code above just an example or your real code?
I guess this question is asked because your source makes most of us squeam in horror. Others have already pointed out that you should use regular
main()
for Qt programs. It doesn't help that you are creating your ownargc
andargv
variables to be passed to the QApplication constructor.It is really bad practice to use global variables. Everything above your main function should go into a
MainWindow
class derived fromQMainWindow
.You should also avoid using pointers whereever possible. In your particular case there is no reason for
Application
andmainWindow
to be pointers (butmainWindow
should also be a local variable). Theoretically, you are leaking memory. (Practically, it doesn't matter because the OS will clean up after you.)@JanLaloux said in Problems with stacked Progress Dialogs:
I don't agree. If there is only the global progress window (no step progress shown), the progress dialog remains responsive and it can be cancelled at any time, no other thread is needed. You have of course to check regularly if the cancel button was pressed, and the QProgressDialog::canceled signal is connected to a slot for this.
My experience is totally different. I have never seen a smooth animation of the progress bar when doing it this way. And you also have to hit the cancel button at the right time to be able to cancel progress. This is the reason why you'll find many discussions in this forum using QApplication::processEvents to make the progress dialog somehow work properly. But, everybody will tell you not to use QApplication::processEvents. Your actual computation will take way longer if you do this. The only proper way to do this is actually to use a separate thread. I have simplified this for myself and put it into a library: https://github.com/SimonSchroeder/QtThreadHelper. Just use
workerThread([](){ /* do stuff */ });
to run any function in another thread (this is derived from QThread::create followed by start()). If you want to wait for the work to finish, useworkerThreadJoin()
instead. There's another helperguiThread()
which I typically use to update the progress bar. You'll find an example in the readme of my lib.@SimonSchroeder Waw, this is realy helpfull (and not just a squeam in horror ;-), thank ypu! I will have a close look into your library and rework the whole lot in this way. Will take some time, but I'll give you feedback when it's done.