Qt QProgressBar indeterminate - c++

My application needs to do some operations which might take a second but might also take 10 minutes. For this purpose I need to show a QProgressDialog with indeterminate QProgressBar during the operation.
QProgressDialog dlg( this );
dlg.setBar( new QProgressBar() );
dlg->setMaximum( 0 );
dlg->setMinimum( 0 );
dlg.setModal( true );
dlg.show();
//operation ...
dlg.close();
During my operation the dialog shows up, is transparent, has no progressbar and after the operation it closes.
Does anyone know what I can do to show a modal dialog which prevents the user from interacting with the application and which shows the user an indeterminate progressbar?

I'd suggest you don't create your own QProgressBar. The QProgressDialog has its own bar inside, and propagates all the methods from dialog to bar. What's more, to make your window modal use exec not show, or setModal(true) first. To close it, connect some signal (of a finished work) to the cancel() slot (or user will have to click Cancel button).
QProgressDialog dialog;
dialog.setRange(0,0);
dialog.exec();

I think one thing that you might need is that you call QApplication::processEvents() while looping over your entries.
Quoting from QCoreApplication docs:
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
and I think in this particular case the application will not update the appearance of your QProgressDialog while it is busy performing the long operation unless you call QApplication::processEvents().
If you have fixed range and you call setValue() as your loop progresses (quoting from the QProgressDialog docs):
If the progress dialog is modal (see QProgressDialog::QProgressDialog()), setValue() calls QApplication::processEvents()
(I am omitting here the warning which cautions that this can cause issues with re-entrancy).`
Note that when I tried your code it created a dialog like what you would expect if you only remove the line
dlg.setBar( new QProgressBar() );
As was said in another answer, QProgressDialog has its own QProgressBar so unless you have special requirements, this should do what you need.

The documentation of the QProgressDialogs method setBar states, that "... the progress dialog takes ownership of the progress bar ...", however I found a forum post about the exact same problem you have.
In that post the last answer of the user ShaChris23 states, that this problem can be solved by passing the pointer to your QProgressDialog in the constructor of the QProgressBar, to set it as parent:
dlg.setBar( new QProgressBar(&dlg) );

Related

QProgressDialog "closes" when another process is started

In my project, I have a function running while a QProgressDialog shows the progress.
QProgressDialog progress("Saving savegame.dat...", "Abort Save", 0, 3016, this);
progress.setWindowModality(Qt::WindowModal);
//... some loops and other calculations run while I update the progress bar with:
progress.setValue(1000);
All is well until I start another process. (Open a cli program)
QProcess decomBR;
QStringList filePathListBR;
filePathListBR.append("-o");
filePathListBR.append("stuff\\compress.bms");
filePathListBR.append("stuff\\regions\\xbox_chunks\\br");
filePathListBR.append("stuff\\regions\\xbox_chunks\\br");
decomBR.start("stuff\\quickbms.exe", filePathListBR);
decomBR.waitForFinished();
As soon as a process like this is started, the progress bar dialog hides or something and the progress is no longer shown, but the processes still run fine.
Any way to prevent these processes from "closing" the QProgressDialog?
EDIT: So apparently, the dialog isn't closing, it's just the main window is taking priority and "covers" the dialog... if that makes sense. Is there any way to make the dialog maintain display priority?
Thanks for your time :)
I have not tried this, but setWindowFlags(Qt::WindowStaysOnTopHint); may help. Notice that it's a flag, so you'd want to write something like:
progress.setWindowsFlags( progress.getWindowsFlags() | Qt::WindowStaysOnTopHint );
Consider using an assertion to see if it's already set, if so then you can dismiss my answer definitively and add to human knowledge by negation!

How to return from exec method programmatically in static singleton class

I am developing Qt application (Qt version 4.7.3) on SBC6000x board.
I have a MessageBox class derived from QDialog. I have made this class singleton.
Whenever a messagebox is to be show I am using .exec method to show it.
There are few places where I need to show messageboxes one after another.
So, to show new messagebox, I have to close previous one and show new one.
e.g. When Messagebox is open and at same time I receive an error from background I have to close the messagebox which is currently shown and show the one with error.
To closes previous dialog I have exposed CloseDlg method from messagebox class and trying to close it.
Inside this CloseDlg I am emitting finished signal.
void CMsgBox::CloseDlg()
{
if (NULL != CMsgBox::m_msgBox)
{
if(CMsgBox::m_msgBox->isVisible())
{
emit CMsgBox::m_msgBox->finished(0);
//QApplication::processEvents();
}
}
}
and calling it as
CMsgBox::CloseDlg();
My show method is :-
int CMsgBox::showMsgBox(Icon icon, const QString &textMsg, const QString &okBtnText)
{
if (CMsgBox::m_msgBox == NULL)
{
CMsgBox::m_msgBox = new CMsgBox();
}
CMsgBox::m_msgBox->setText(textMsg);
CMsgBox::m_msgBox->setIcon(icon);
CMsgBox::m_msgBox->setOkBtnText(okBtnText);
CMsgBox::m_msgBox->exec();
return CMsgBox::m_msgBox->m_btnPressed; //return, unblock the call
}
Again when I call showMsgBox,it is showing me following warning.
QDialog::exec: Recursive call detected
Problem is, it doesn’t return from previous exec call (unless we return, as commented above //).
I tried same with close(), accept(), reject() methods instead of finished() event but nothing worked.
What is the way to return from previous exe call and achieve above scenario? Any help is welcome.
What you have here looks like a race condition. A modal QDialog runs its own event loop, so your application behaves like a multithreaded application and you need to take care of concurrency and race conditions.
When you receive a second in your main event loop, you call CMsgBox::CloseDlg() and CMsgBox::showMsgBox() in quick succession. However, CloseDlg() tells the dialog's event loop to return, but CloseDlg() actually returns before the dialog's event loop is done cleaning up, and showMsgBox() attempts to call exec() on a dialog which hasn't finished exiting yet.
What you need to do is, when you call CMsgBox::CloseDlg(), connect to the finished(int) signal, and only when you receive the finished(int) can you safely exec() the dialog again.
NOTE: When connecting to the finished(int) signal, make sure to use a Qt::QueuedConnection instead of a Qt::DirectConnection which is the default.
So, you need modeless dialog box. As explained in their documentation :
Modeless dialogs are displayed using show(), which returns control to the caller immediately.
Therefore, instead of showing the box with exec(), show it with show().
Alternative to show() method suggested in another answer is, use QDialog::open(). It will return, but will still give you modal dialog, so the rest of the GUI will be disabled until you close it.

Test modal dialog with Qt Test

I am trying to write a unit test for a GUI application using the QTestLib. The problem is that one of the slots creates a modal dialog using exec() and I found no possibility to interact with the dialog.
The slots which creates the dialog is connected to a QAction. So the first problem is that the test blocks when I trigger the QAction in the test since this results in the call to exec(). Therefore, I tried creating a QThread that performs the interaction. However, this did not help.
Things I already tried (all performed from within the "interaction helper" thread):
Send key clicks using QTest::keyClicks()
Results in error message "QCoreApplication::sendEvent(): Cannot send events to objects owned by a different thread"
Post QKeyEvents using QCoreApplication::postEvent()
Doesn't work, i.e. nothing happens. I guess because the events end up in the event loop of the thread that owns the dialog, which will not be reached until the dialog is closed and exec() returns. See Edit below
Invoking Slots on the dialog using QMetaObject::invokeMethod()
Doesn't work, i.e. nothing happens. I guess for the same reason as postEvent() doesn't work. See Edit below
So the question is: Is there any way to interact programmatically with a modal dialog that was opened using the exec() method?
Edit: Actually, method 3 is working. The problem was a different one:
I passed the arguments to invokeMethod() to the "interaction helper" thread and for some reason, accessing the arguments did not work from that thread (I got no SEG errors but they were simply empty).
I guess that method 2 is also working and I simply had the same problem as with method 3 but I didn't test that.
The solution I use in command line applications which use Qt libraries meant for GUIs is the singleShot, as this answer alludes. In those cases it looks like this:
QCoreApplication app(argc, argv);
// ...
QTimer::singleShot(0, &app, SLOT(quit()));
return app.exec();
So in your case I imagine it would look something like this:
QDialog * p_modalDialog = getThePointer(); // you will have to replace this with
// a real way of getting the pointer
QTimer::singleShot(0, p_modalDialog, SLOT(accept()));
p_modalDialog->exec(); // called somewhere else in your case
// but it will be automatically accepted.
You can keep the interaction in the same thread by delaying its execution until the dialog event loop starts.
For example just before the exec() call, you use either QTimer::singleShot with 0 as interval or QMetaObject::invokeMethod with connection type Qt::QueuedConnection to invoke the slot that needs to be executed while the dialog is shown.
You can also post the events before calling exec().
As soon as the dialog has been constructed after the exec() call, the events will be executed.
For example to test an Esc key press (means reject/close the dialog):
// create a dialog
QDialog d = ...
//post an Escape key press and release event
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyPress , Qt::Key_Escape, Qt::NoModifier) );
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier) );
// execute and check result
int ret = d.exec();
QCOMPARE(ret, static_cast<int>(QDialog::Rejected));
related question's answer has some extra details about flushing the event queue during a test:
Qt event loop and unit testing?

Qt problem: No QProgressBar animation with minimum and maximum steps set to 0

I have got a problem with my QProgressBar and I hope someone got an idea...
I have created a progress dialog with a QProgressBar on my own. I set minimum and maximum steps to 0 so that the progress indicates my program is busy (the animation thing...).
I show() this progress dialog and activated the Qt::WindowModal for this dialog.
The problem: I use this dialog while copying files but the progress bar stops and no animation anymore to indicate my program is still busy. I use the windows function 'SHFileOperation' to copy one directory with a lot of file to a destination. This, of course, produces a lot of load on the system but at least the progress should continue moving.
Any help is appreciated!
Thanks in advance,
BearHead
The problem is that the SHFileOperation call will block the main event loop. Therefore, no events will be processed preventing the QProgressBar from being updated.
To fix this you could perform the copy action in a separate thread. The easiest way to go about this is using Qt Concurrent, for example as follows:
QFuture<void> future = QtConcurrent::run(SHFileOperation, ...);
QFutureWatcher<void> watcher;
connect(&watcher, SIGNAL(finished()), dialog, SLOT(close()));
Assuming dialog is a pointer to your progress dialog.
Btw, why do you use SHFileOperation instead of the operations provided by QDir and QFile?

Reopen modal dialog in MFC

I need to open a dialog box instantiated from the same class twice. When I try this
CdelmeDlg dlg;
dlg.DoModal();
dlg.DoModal();
The second call opens the dialog only for a split second, then it is closed. My bet was there is a leftover message in the message queue, so I added this in between the calls
MSG msgCur;
while (::PeekMessage(&msgCur, NULL, NULL, NULL, PM_REMOVE))
;
This solves the problem, but it feels like a wrong kind of thing to do. Is there a way to process the leftover message properly?
Don't call EndDialog( IDOK );
To handle the ok or cancel buttons being pressed just inherit OnOk or OnCancel ... Otherwise EndDialog is going to be called twice and you'll get the problem you are getting!
I actually think that YeenFei has a good point here.
It's been a while since i've played with MFC (thank goodness), but from memory, a timer, may or may not be called from the UI thread depending on which one you use. If the timer is being raised on the main UI thread, then a modal dialog will likely halt the main thread until it is dismissed, after which it would be called by the next timer. If the timer is raised on a separate thread, then your dialog is not blocking the main UI thread as it is being shown on a separate thread.
It's does seem more conceivable as YeenFei has pointed out that you want to re-show your dialog each time the timer is raised, hiding it when the user clicks on the button to dismiss it. That way, if the time is raised again, all it does is re-show the dialog whether it is currently open or not.
There is a great post here (www.eggheadcafe.com) about Timers and concurrency that you may find interesting, and may make things clearer than what I managed to accomplish.
Why can't you code it like this:
CdelmeDlg dlg;
dlg.DoModal();
CdelmeDlg dlg1;
dlg1.DoModal();
If you want your application run in background without UI, why not juz temporary hide it? a simple function this->ShowWindow(SW_HIDE) will do the job for you.
i think you should revise your design decision as it seem illogical for an application to behave like what you wanted it to be.
I solved the problem by hiding the dialog instead of closing it and launching another thread that first sleeps and then unhides the dialog.
May be your code has line:
m_pMainWnd = &dlg;
If so, than application after first call of DoModal() will finished, all other call of DoModal() will return -1. From MSDN:
The Microsoft Foundation Class Library will automatically terminate your thread when the window referred to by m_pMainWnd is closed. If this thread is the primary thread for an application, the application will also be terminated.