(seemingly) simple Qt app: creating a progress bar
-
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();
}
@ -
m_worker should have a value as soon as you say m_worker = new WorkerThread(); Where are you doing that? In setupWidgets()? If so, can you show that code, as well?
-
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();
}
@ -
I've run through the program in the debugger a few times. I'm pretty sure that the
m_worker->requestFinish() slot doesn't execute until the m_thread->wait() has timed out. Is it possible that the wait is somehow blocking the delivery of the slot? -
Usually ,This link error means you have a member function defined but not implemented it
-
I'm not sure why you don't want to use just what the Qt provided. I usually use QProgessBar and QCoreApplication::processEvents to do this. like:
@
QProgressBar bar(...);
do {
do something;
bar.setValue();QCoreApplication::processEvents ();
}
@Edit: Code formatting. Please wrap code in @ tags. -mlong]
-
foxyz: I'm not sure I understand you...are you suggesting a different approach to what has been posted in this thread?
I'm pretty sure the current implementation is close to working; I just need to find what's causing the signal not to be delivered in a timely fashion.
-
Foxyz's proposing a non-threaded approach where you embed manual event processing within your working logic. While it is technically possible to do that, I believe the threaded approach you're taking currently is a much cleaner solution.
-
From a novice's point of view, they both seem to have their advantages. It looks (from a first glance) that foxyz's approach would result in fewer signals/slots/connects, and that's where I seem to be stumbling right now. On the other hand, the approach that mlong and Volker are showing me is HIGHLY transferable to an existing program. Once I get this working right, it seems to be a matter of putting my (non-Qt) main code into the run() routine, establishing the sync between the worker and the UI threads, and that's it.
Any idea on my question above?
Thanks.
-
While the embedded event processing has a certain appeal to it, you still are limited by the amount of work being done in between calls to processEvents(), and in certain situations you have to make sure that your GUI, etc, is not able to reenter your worker code because of getting some sort of event to restart it, otherwise things can get hairy.
As for the other question, I don't have anything helpful to add at the moment...
-
OK, thanks, mlong. Any tips on putting other breakpoints or qDebug telltales anywhere for more information on what's going on?
BTW, the one remaining line of code in main.cpp is a connect() that I commented out:
@// app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
@The presence or absence of this call isn't relevant to the current problem, is it?
-
Can you put your current code into a ZIP for downloading? Just replace the actual workload in the run method with some dummy loop. It's hard to say what's going on without seing the whole picture.
-
Sure, I can do that. Where would you like me to put it? Or, I can email it to you.
Thanks, Volker.
EDIT: Just remembered this site. Here's the zip:
http://www.mediafire.com/download.php?qp6lelkebbni20i
Thanks again.
-
Ok, I made it run as expected. First, I had a mistake in my outline of the onQuitButtonClicked() method. The call to close() wasn't conditional, it was always called, that's why your app close, even if you clicked on "no".
To make it run:
add a signal workDone() to WorkerThread:
@
signals:
void percentChanged(int percent);
void workDone();
@emit the signal at the end of the run method:
@void WorkerThread::run()
{
qDebug() << "WorkerThread::run(): STARTED";// your code goes here. myFile.close(); qDebug() << "WorkerThread::run(): finished"; emit workDone();
}
@in the MainWindow constructor connect the signal:
@
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// ...connect(m_worker, SIGNAL(workDone()), m_thread, SLOT(quit())); // ...
}
@modify MainWindow::onQuitButtonClicked() to this version
@
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) { if(m_thread->isRunning()) { qDebug() << "thread is running - trying to terminate it"; emit requestWorkerToFinish(); // no direct call! bool finishOk = m_thread->wait(10000); if (!finishOk) { qDebug() << "WARNING: request to finish thread timed out!"; m_thread->terminate(); m_thread->wait(); } } else { qDebug() << "Thread not running"; } close(); }
}
@Some additional hints:
At the end of a method that has no (void) return type, you do not need to call return explicitly.
All the QObjects you create using new() should have a parent:
@
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
m_bar = new QProgressBar (this);
m_button = new QPushButton (this);
m_worker = new WorkerThread (this);
m_thread = new QThread (this);setupWidgets(); // ...
}
@If they do have a parent, you do not need to delete them manually, so remove the deletes from the destructor:
@
MainWindow::~MainWindow()
{
}
@And to have a nice layout, you should put everything into a layout or use a Qt Designer based form.
-
Hmm...it's not working as I'd expect it to. Here's the app output when I push the "yes" button:
bq. WorkerThread::run(): STARTED
thread is running - trying to terminate it
WorkerThread::run(): finished
reached end of file.
WARNING: request to finish thread timed out!
The program has unexpectedly finished.Based on my telltales in run(), it appears that the slot requestFinish() isn't called until run() is finished. I added a telltale to requestFinish(), and here's the output I got:
bq. WorkerThread::run(): STARTED
thread is running - trying to terminate it
reached end of file.
WorkerThread::requestFinish() called.
WorkerThread::run(): finished
WARNING: request to finish thread timed out!
The program has unexpectedly finished.Another issue: this line in mainwindow.cpp doesn't compile:
@ m_worker = new WorkerThread (this);
@Perhaps this is because my c'tor didn't have an argument, so I modified its declaration to be this:
@ WorkerThread(QObject * parent = 0);
@Now it compiles (though I get a compiler warning that parent is never used). Is this OK?
-
Nope, you must also change the constructor implementation:
@
WorkerThread::WorkerThread(QOjbect parent)
: QObject(parent)
{
// ...
}
@It is always a good idea to always add the parent parameter to any QObject subclass. If you use the new class wizard in Qt Creator, it's even done automatically, if you state that it is a QObject subclass :-)
Regarding the finish: I need to prepare a test case, my files were far too small to add reasonable workload to the worker thread. It was always finished before I had the chance to click the button :)
-
Hmm, I now get a run-time warning/error:
bq. QObject::moveToThread: Cannot move objects with a parent
The documentation on QObject confirms this.
Also:
bq. And to have a nice layout, you should put everything into a layout or use a Qt Designer based form.
...meaning, that I don't use Designer to create the UI, but I somehow move my existing stuff into Designer context? If you could point me at a page that talks more about this, I'll get started on it.
Thanks.