(seemingly) simple Qt app: creating a progress bar
-
Oh, yes...because QProgressBar already has the smarts to know what to do with this, right? Got it. Thanks, Volker.
OK, I guess the next step is to empower the close button. (Then maybe a start/stop button, and that'll be that for this exercise.) I gather that I'm probably to use this signal:
@void QAbstractButton::clicked ( bool checked = false ) [signal]@
Assuming this is right:
- would the connect() call most appropriately go within the MainWindow constructor?
- I gather the first half of the connect() would look like this:
@ connect(&m_button, SIGNAL(clicked(bool)), )
@but what would be the object for the third parameter? And, what is the signal to QMainWindow to get it to close? I can't find anything in the doc about closing (except in the saveState() method, which I'm pretty sure isn't relevant to this).
Thanks for the help.
-
The quick and dirty option would be:
@
connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
@This just terminates the QApplication object, which in turn shuts down the rest of your application. Note that the bool parameter is not necessary here. It's sufficient to know that the button was clicked.
This is quite "rude", though, as it does not ask the user, if it was ok to quit the application and the worker thread doesn't get a chance to finish its work cleanly. So what you could do, is connect the signal to a slot in the main window class, ask the user if he/she really wants to quit the application (using one of the static [[Doc:QMessageBox]] methods). If yes, send the thread a signal to terminate its work, wait until the thread has finished (using wait, probably with a timeout). Some outline would be:
@
// move the thread and the worker out of the main method
// to the MainWindow class// so you have
QThread *m_thread;
Worker *m_worker;// define a signal
requestWorkerToFinish();// in constructor of MainWindow:
connect(m_button, SIGNAL(clicked()), this, SLOT(onQuitButtonClicked()));
connect(this, SIGNAL(requestWorkerToFinish()), m_worker, SLOT(requestFinish()));// you have to implement requestFinish in the worker!
void MainWindow::onQuitButtonClicked()
{
QMessageBox::StandardButton answer = QMessageBox::question(
this,
tr("Quit applicaton?"),
tr("Do you really want to quit the application?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);if(answer == QMessageBox::No) // user clicked no, so just continue as if nothing had happened return; emit requestWorkerToFinish(); // no direct call! bool finishOk = m_thread->wait(30000); if(!finishOk) { qDebug() << "WARNING: request to finish thread timed out!"; m_thread->terminate(); m_thread->wait(); } close();
}
@This is brain to terminal and untested. Please expect errors :-)
Just a hint: if you do like outlined here, don't forget to reimplement closeEvent() in the main window. Otherwise your users are able to just nuke off your application by clicking on the (X) button in the title bar of the window!
-
Hi, Volker -
Since I'm not exactly breezing through this stuff, I'd like to take it step by step, so I'll begin with the quick and dirty option.
- Just to keep my example program consistent with the code snippets in this thread, I tried renaming my QApplication from app to qApp. The build objected, claiming:
bq. error: no matching function for call to 'QApplication::QApplication(QApplication*)
I searched the docs for "qApp" and found that it's a global pointer, so evidently I shouldn't have tried to use it as a variable name. So, I reverted to the original variable name.
- from what context (function) should the above connect call be made? If I do it in the mainwindow c'tor, it doesn't "see" my QApplication object (which lives in main()).
I suspect that the answers to these two issues are related. Can you tell me what's going on here? Should my QApplication object be global or something?
I can post code if desired. Thanks.
-
The purpose of the qApp macro is to provide a global access point to your QApplication. It's equivalent to the static method QCoreApplication::instance().
So long as you #include <QApplication> in a piece of code, then qApp will provide you with global access to your application instance.
Edit to add:
QCoreApplication (and, as such, QApplication) is an example of the "Singleton pattern.":http://en.wikipedia.org/wiki/Singleton_pattern As such, it inherently has global qualities.
-
Well, then...I'm doing something wrong. Here's a couple of code snippets:
main.cpp:
@#include <iostream>#include <QObject>
#include <QApplication>
#include <QString>
#include <QThread>#include "workerthread.h"
#include "mainwindow.h"using namespace std;
int main(int argc, char *argv[])
{
QApplication app (argc, argv);
MainWindow mainWindow;
.
.
.@and mainwindow.cpp:
@#include <QApplication>#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
m_bar = new QProgressBar (this);
m_button = new QPushButton (this);setupWidgets();
connect(&m_button, SIGNAL(clicked()), app, SLOT(quit()));
}
.
.
.
@I'm getting a compiler error that app isn't declared in mainwindow.
-
bq. I’m getting a compiler error that app isn’t declared in mainwindow.
It's not. The variable name "app" in main() has no bearing. It's not really a global. That's why you use qApp in other parts of your program. It's a shortcut that always refers back to "The QApplication" (There can only be one QApplication in your program, so it always "knows" what you're talking about.)
@
connect(&m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
@ -
Ahh...I suppose I should have figured that out. Thanks, mlong. Just for keeping the thread "good," I should point out another error in my code:
@ connect(&m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
@
should actually be:
@ connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
@As m_button is a pointer.
Anyway...it works! (Yes, I'm excited...I've learn to measure progress in small increments with respect to my education in Qt.) So...now that the "rude" implementation works, I'm ready to start on the more "polite" version that Volker implemented above. I'll be back with more newbie questions.
Thanks, mlong.
-
requestFinish() is only a signal in class [[Doc:QHttp]] - unless exactly that is your worker, you will need to define that slot in your worker class and act accordingly (e.g. setting a bool variable to true and check that regularly in your loop in order to finish it prematurely).
-
Which docs are you looking at?
You're supposed to define your own slot in your thread to start the "finish everything up and shut down" process there. Thus the comment, "// you have to implement requestFinish in the worker!"
There's nothing special about that particular slot name. Is just an example.
-
Oops...my bad. I didn't read the documentation carefully. Thanks, guys.
So, I defined the slot requestFinish(), which sets a bool that an "end" request was received. In my loop in run(), I test this bool. If it's true, I terminate the loop, which drops me to a file.close() and the end of the run() routine.
I made my connect calls in my MainWindow c'tor:
@ connect(m_button,
SIGNAL(clicked()),
this,
SLOT(onQuitButtonClicked()));
connect(this,
SIGNAL(requestWorkerToFinish()),
m_worker,
SLOT(requestFinish()));
m_worker->moveToThread(m_thread);
@Now I get a run-time error:
bq. QObject::connect: Cannot connect MainWindow::requestWorkerToFinish() to (null)::requestFinish()
Is the reason I'm getting the (null) because I haven't yet called m_thread->start()? I guess I'm not too clear on the proper order of things in the c-tor (or if it's even OK to do this stuff in the c'tor).
Thanks.
-
I've created the pointer, but perhaps it doesn't get "populated" until the start() call?:
@MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
m_bar = new QProgressBar (this);
m_button = new QPushButton (this);setupWidgets();
connect(m_button,
SIGNAL(clicked()),
this,
SLOT(onQuitButtonClicked()));
connect(this,
SIGNAL(requestWorkerToFinish()),
m_worker,
SLOT(requestFinish()));
m_worker->moveToThread(m_thread);// run the run() method of the worker object once the thread has started
QObject::connect(m_thread, SIGNAL(started()), m_worker, SLOT(run()));
// enable the updating of the progress bar
QObject::connect(m_worker,
SIGNAL(percentChanged(int)),
this,
SLOT(setBarValue(int)));
m_thread->start();
}
@ -
Well, hell...it was there a while ago!
Somehow, in the movement of code from main.cpp to mainwindow.cpp, that call must have gotten lost. I put it just below the two other "new" calls in the c'tor above; is that a reasonable place for it?
Now, though, this line of code:
@ QObject::connect(m_thread, SIGNAL(started()), m_worker, SLOT(run()));
@
Is giving me this error:bq. QObject::connect: Cannot connect (null)::started() to WorkerThread::run()
(Probably due to this, it later bombs somewhere in the start() routine.)
So...evidently I still don't have the order correct yet.
EDIT:
OK, I just realized I'd made the same mistake with m_thread. That too is now in the MainWindow c'tor. Duh...
Program now displays the main window and begins running. When I push the "close" button, I get the dialog box asking me if I really want to quit. But...the subsequent behavior is odd. When I press "no," the program exits immediately. When I press yes, it waits 3 seconds (I changed the wait() in Volker's example from 30 to 3 seconds) then I get a timed-out warning, then the program exits.
Is the worker thread supposed to send a signal back to mainWindow that it's completed?
-
A QThread sends a signal that it has finished (see the docs). But for the wait call, you don't need to connect something to that signal. One would need a test run to actually see what's going wrong here.
Best thing in this case would be to put some qDebug() outputs in the worker thread. Make sure, that the worker actually receives the signal. For the immediate exit on "no", you should run in a debugger and step through your program to see what actually happens. Did you happen to call exit instead of return?
-
bq. Did you happen to call exit instead of return?
In which routine: workerThread::run()? I don't call anything there; I just let the routine run to completion:
@void WorkerThread::run()
{
long curr, percent;
string myStr;
static long counter = 0;fileSize = getFileSize(myFile);
while (myFile.good() && !endSignaled)
{
myFile >> myStr;
curr = myFile.tellg();// special handling for end of file.
if (curr != -1)
percent = curr * 100 / fileSize;
else
percent = 100;counter++;
if ((counter % 20 == 0) || (percent == 100))
emit percentChanged(percent);
}
myFile.close();
}
@ -
I'm stepping through the program in the debugger. From what I'm seeing, I'd guess that the requestFinish() slot is never getting called. When I hit close, then yes, the program continues to run in the background until EOF is hit, at which point run() ends naturally. (This brings up a side point: is it considered OK to allow the worker thread to continue until the confirmation button is hit, or should it pause when the close is hit?)
Here's the c'tor of my MainWindow, in case it helps:
@MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
m_bar = new QProgressBar (this);
m_button = new QPushButton (this);
m_worker = new WorkerThread ();
m_thread = new QThread ();setupWidgets();
connect(m_button,
SIGNAL(clicked()),
this,
SLOT(onQuitButtonClicked()));connect(this,
SIGNAL(requestWorkerToFinish()),
m_worker,
SLOT(requestFinish()));// run the run() method of the worker object once the thread has started
QObject::connect(m_thread,
SIGNAL(started()),
m_worker,
SLOT(run()));// enable the updating of the progress bar
QObject::connect(m_worker,
SIGNAL(percentChanged(int)),
this,
SLOT(setBarValue(int)));m_worker->moveToThread(m_thread);
m_thread->start();
}
@