February 12, 2012

mzimmers mzimmers
Ant Farmer
524 posts

(seemingly) simple Qt app: creating a progress bar

Page  
1

Hi, all –

Some of you know me already: I’ve been using Qt as an IDE for about a year now (and love it). I’ve been trying to convince a reluctant management to let me begin experimenting with putting a Qt UI on a desktop app I’m building, and now have a short window of opportunity. I think I’d like to begin with something ultra simple: our program reads a data file, processes it and produces output files. I’d like to add a progress bar to help the user monitor how far through the input file the program is.

Since my real program is way too complex to get into here, I’ve created a stub program that I think contains the essentials for this:

  1. #include <iostream>
  2. #include <fstream>
  3. #include <string>
  4.  
  5. using namespace std;
  6.  
  7. long getFileSize (fstream& f)
  8. {
  9.  long end;
  10.  
  11.  f.seekg (0, ios::end);
  12.  end = f.tellg();
  13.  f.seekg(0, ios::beg);
  14.  
  15.  return end;
  16. }
  17.  
  18. int main()
  19. {
  20.  long curr, end, percent;
  21.  string myStr;
  22.  fstream myFile("test.pro.user");
  23.  
  24.  end = getFileSize(myFile);
  25.  
  26.  while (myFile.good())
  27.  {
  28.   myFile >> myStr;
  29.   curr = myFile.tellg();
  30.  
  31. // special handling for end of file.
  32.  
  33.   if (curr != -1)
  34.    percent = curr * 100 / end;
  35.   else
  36.    percent = 100;
  37.  
  38.   cout << percent << " percent finished." << endl;
  39.  }
  40.  
  41.  myFile.close();
  42.  
  43.  return 0;
  44. }

So, I’ve been doing some reading of the docs, and (as usual) I’m a little lost in how to get started. I don’t expect anyone to do this for me, but could someone point me in the right direction? I believe I want to use this:

Qt page on progress bar [developer.qt.nokia.com]

…but I’m sure there’s more to it than just including this widget.

Any guidance would be appreciated. Thanks so much.

I’ve already gotten a development team across the continent to use Qt for their IDE, so my evangelism is making (slow) progress. I think if I can pull off this, and a few embellishments, they’ll let me use Qt as the tool for the UI in an upcoming project with large commercial implications.

71 replies

February 12, 2012

sierdzio sierdzio
Area 51 Engineer
2334 posts

I think you would be much better off if you start developing your app in Qt. Starting in std and then migrating to Qt is harder – especially when you are learning.

Standard way here would be to update progress bar using signal and slot mechanism, but that would require a big rewrite of your code. For something simpler that might (I’ve not tested) work, try this:

  1. #include <QApplication>
  2. #include <QProgressBar>
  3.  
  4. int main()
  5. {
  6.  long curr, end, percent;
  7.  string myStr;
  8.  fstream myFile("test.pro.user");
  9.  
  10.  end = getFileSize(myFile);
  11.  
  12.  QProgressBar bar(0);
  13.  bar.setRange(0, 100);
  14.  bar.setValue(0);
  15.  bar.show();
  16.  
  17.  while (myFile.good())
  18.  {
  19.   myFile >> myStr;
  20.   curr = myFile.tellg();
  21.  
  22. // special handling for end of file.
  23.  
  24.   if (curr != -1)
  25.    percent = curr * 100 / end;
  26.   else
  27.    percent = 100;
  28.  
  29.   bar.setValue(persent);
  30.  
  31.   cout << percent << " percent finished." << endl;
  32.  }

I’m not sure it will work, I would normally have done it in a much different way. For an example app, you can take a look at my sPDaR [gitorious.org], but proceed with caution, as this is a very old and crude code :) Works, though.

 Signature 

(Z(:^

February 12, 2012

Volker Volker
Robot Herder
5428 posts

Be aware, that updates to the progress bar are more or less blocked as long as the while loop is running. To make the application responsible while the work is done, one could consider using a worker thread. As a workaround, one should at least run the even loop handler after changing the progress bar’s values. A QProgressDialog can be an alternative too, it suffers from a non running event loop too.

February 12, 2012

mzimmers mzimmers
Ant Farmer
524 posts

Volker: I’m not sure I understand. Are you saying that this isn’t feasible?

  1. while (something)
  2. {
  3.    do some work
  4.    update to reflect the progress of the work
  5. }

February 13, 2012

Volker Volker
Robot Herder
5428 posts

You can do this, but the event loop must be run regularly, so that the events (e.g. repaints, mouse events, key press event, and so on) are handled. As long as you are within the while loop, this is only done if you call QApplication::processEvents() manually. Otherwise the the application is blocked, on the Mac it usually shows the rotating, colored “beach ball” and on Windows that “this application doesn’t react” message.

February 13, 2012

mzimmers mzimmers
Ant Farmer
524 posts

OK, so if I can pursue this a bit further:

1. the application, once started is completely compute-bound. No action from the user is needed nor permitted.
2. in my “real” app, it executes about 750 loops per second. (I’m talking about my compute loops, not the event loops).

But, I’m left with the impression that you don’t like this idea. So, returning to the original example, what would have been a preferred design?

Thanks.

February 13, 2012

Volker Volker
Robot Herder
5428 posts

In that case I would move the actual computation to a worker thread. Use the pattern that’s recommended nowadays: Create a QObject subclass that does the work and QThread as a managing helper only (no subclassing of QThread). The Threads, Events and QObjects wiki article has the details. Your worker object defines a signal, say progress(int value, int max) that you emit, say every 200 iterations of your loop (in every iteration it would just flood the app with events!). The main thread does little less than opening a progress bar (or progress dialog) and spins the event loop for the GUI.

February 13, 2012

mzimmers mzimmers
Ant Farmer
524 posts

OK…I scanned the article you identified. I was taken by this line:

Nine times out of ten, a quick inspection of their code shows that the biggest problem is the very fact they’re using threads in the first place, and they’re falling in one of the endless pitfalls of parallel programming.

So…am I “doing this wrong?” I’ll go this route if you think it’s best, but I’m open to other suggestions (as long as they don’t entail a complete re-write of my app; the boss won’t go for that).

Back to the example I posted originally: would you (hypothetically) use threads for that as well?

February 13, 2012

Volker Volker
Robot Herder
5428 posts

In your case I would say that going with multithreaded architecture is ok. That 9-of-10 statement referes to stuff where you don’t need MT at all, like putting an already asynchronous API like QNAM or sockets into a thread to make it asynchronous.

To answer the last question: yes, I would use a thread for this.

February 13, 2012

mzimmers mzimmers
Ant Farmer
524 posts

OK, thanks, Volker…I guess I’ve got some reading to do. I appreciate the feedback, as always.

Edit: Volker, I sent you an email with a couple of questions; if you’re so inclined to respond, that would be great.

February 17, 2012

mzimmers mzimmers
Ant Farmer
524 posts

OK, I’ve done a little work on this. I know it’s incomplete, and I’m sure it’s not right yet, but…I’m ready to ask for some feedback. Thanks…

  1. #include <iostream>
  2. #include <fstream>
  3. #include <string>
  4.  
  5. using namespace std;
  6.  
  7. #include <QApplication>
  8. #include <QProgressBar>
  9.  
  10. long getFileSize (fstream& f)
  11. {
  12.  long end;
  13.  
  14.  f.seekg (0, ios::end);
  15.  end = f.tellg();
  16.  f.seekg(0, ios::beg);
  17.  
  18.  return end;
  19. }
  20.  
  21. class WorkerThread : public QObject
  22. {
  23.  Q_OBJECT
  24. private:
  25.  fstream myFile;
  26. public:
  27.  WorkerThread() : myFile("test.pro.user") {}
  28.  
  29.  void run()
  30.  {
  31.   long curr, end, percent;
  32.   string myStr;
  33.  
  34.   end = getFileSize(myFile);
  35.  
  36.   while (myFile.good())
  37.   {
  38.    myFile >> myStr;
  39.    curr = myFile.tellg();
  40.  
  41.    // special handling for end of file.
  42.  
  43.    if (curr != -1)
  44.     percent = curr * 100 / end;
  45.    else
  46.     percent = 100;
  47.  
  48. // some kind of signal to be emitted here
  49.  
  50.    cout << percent << " percent finished." << endl;
  51.   }
  52.  }
  53. signals:
  54.  void percentChanged(int percent);
  55. };
  56.  
  57. int main(int argc, char *argv[])
  58. {
  59.  QApplication app (argc, argv);
  60.  WorkerThread worker;
  61.  
  62.  QProgressBar bar(0);
  63.  bar.setRange(0, 100);
  64.  bar.setValue(0);
  65.  bar.show();
  66. // bar.setValue(percent);
  67.  
  68.  return app.exec();
  69. }
  70.  
  71. void WorkerThread::percentChanged(int percent) {}

February 17, 2012

Volker Volker
Robot Herder
5428 posts

On a first glance, that looks good. Despite the fact that you might consider using QFile/QTextStream for the file operations and QFileInfo for getting the file size (instead of your method).

Then you must not define the method for the signal yourself, this is done automatically by moc. It is sufficient to just declare the signal in the class header file.

The signal must be emitted in the run method of your thread:

  1. // some kind of signal to be emitted here
  2. emit percentChanged(percent);

You’ll also have to connect that signal to the progress bar:

  1. connect(worker, SIGNAL(percentChanged(int)), bar, SLOT(setValue(int)));

Also, the worker needs to be moved to a thread, and eventually started:

  1. QProgressBar bar(0);
  2. bar.setRange(0, 100);
  3. bar.setValue(0);
  4. bar.show();
  5.  
  6. WorkerThread worker;
  7. QThread thread;
  8. worker.moveToThread(&thread);
  9.  
  10. // run the run method of the worker object once the thread has started
  11. connect(&thread, SIGNAL(started())), &worker, SLOT(run()));
  12.  
  13. // update the progress bar
  14. connect(&worker, SIGNAL(percentChanged(int)), &bar, SLOT(setValue(int)));
  15.  
  16. thread.start();

to make the autostart work, you must declare WorkerThread::run() as public slot.

February 17, 2012

mzimmers mzimmers
Ant Farmer
524 posts
Volker wrote:
On a first glance, that looks good. Despite the fact that you might consider using QFile/QTextStream for the file operations and QFileInfo for getting the file size (instead of your method)

Good to know about those…thanks. For now, I’ll just leave it as is. The plan is to:

  1. get this working with a minimum of effort on my sample program
  2. transfer the Qt-specific code to my simulator
  3. let the powers-that-be see how cool even a bit of Qt razzle-dazzle is
  4. go whole-hog on the big job that’s hopefully coming up this spring

Then you must not define the method for the signal yourself, this is done automatically by moc. It is sufficient to just declare the signal in the class header file.

Oh yeah; I knew that (sort of).

The signal must be emitted in the run method of your thread:

  1. // some kind of signal to be emitted here
  2. emit percentChanged(percent);

You’ll also have to connect that signal to the progress bar:

  1. connect(worker, SIGNAL(percentChanged(int)), bar, SLOT(setValue(int)));

Also, the worker needs to be moved to a thread, and eventually started:

  1. QProgressBar bar(0);
  2. bar.setRange(0, 100);
  3. bar.setValue(0);
  4. bar.show();
  5.  
  6. WorkerThread worker;
  7. QThread thread;
  8. worker.moveToThread(&thread);
  9.  
  10. // run the run method of the worker object once the thread has started
  11. connect(&thread, SIGNAL(started())), &worker, SLOT(run()));
  12.  
  13. // update the progress bar
  14. connect(&worker, SIGNAL(percentChanged(int)), &bar, SLOT(setValue(int)));
  15.  
  16. thread.start();

to make the autostart work, you must declare WorkerThread::run() as public slot.

Done, done and done. I’m geting a compiler error that “‘connect’ was not declared in this scope.” I’m including QObject; isn’t that where connect lives?

EDIT: does the connect need object context? There isn’t any in the example on the signals/slots doc page.

February 17, 2012

mlong mlong
Mad Scientist
1517 posts

If you’re using connect() outside of a class that’s a subclass of QObject, you’ll need to use the full name of the static method, “QObject::connect()”

 Signature 

Senior Software Engineer
AccuWeather Enterprise Solutions
/* My views and opinions do not necessarily reflect those of my employer.  Void where prohibited. */

February 17, 2012

Volker Volker
Robot Herder
5428 posts

Oh, using QFile and friends is much more easier than the stdlib methods – give it a try! :)

As mark already mentioned, just call QObject::connect(). That method ist static in QObject, so that works without problems.

February 17, 2012

mzimmers mzimmers
Ant Farmer
524 posts
Volker wrote:
Oh, using QFile and friends is much more easier than the stdlib methods – give it a try! :)

I believe you, but…the goal for this exercise is not to show off all of Qt’s programming features, but to impress upon some reluctant management that it needn’t be invasive to a program’s design.

As mark already mentioned, just call QObject::connect(). That method ist static in QObject, so that works without problems.

Cool…that’s the first time I’ve seen that done.

OK…now I’m getting a linker error:

  1. Undefined symbols:
  2.   "vtable for WorkerThread", referenced from:
  3.       WorkerThread::WorkerThread()in main.o

That doesn’t look like a coding problem to me; am I missing a library or something?

Page  
1

  ‹‹ Show/hide top-level submenu at runtime      [SOLVED] How to get readyRead signal for tcp socket on different thread SLOT ››

You must log in to post a reply. Not a member yet? Register here!