(seemingly) simple Qt app: creating a progress bar



  • 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:

    @#include <iostream>
    #include <fstream>
    #include <string>

    using namespace std;

    long getFileSize (fstream& f)
    {
    long end;

    f.seekg (0, ios::end);
    end = f.tellg();
    f.seekg(0, ios::beg);

    return end;
    }

    int main()
    {
    long curr, end, percent;
    string myStr;
    fstream myFile("test.pro.user");

    end = getFileSize(myFile);

    while (myFile.good())
    {
    myFile >> myStr;
    curr = myFile.tellg();

    // special handling for end of file.

    if (curr != -1)
    percent = curr * 100 / end;
    else
    percent = 100;

    cout << percent << " percent finished." << endl;
    }

    myFile.close();

    return 0;
    }
    @

    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":http://developer.qt.nokia.com/doc/qt-4.8/qprogressbar.html

    ...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.


  • Moderators

    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:
    @
    #include <QApplication>
    #include <QProgressBar>

    int main()
    {
    long curr, end, percent;
    string myStr;
    fstream myFile("test.pro.user");

    end = getFileSize(myFile);

    QProgressBar bar(0);
    bar.setRange(0, 100);
    bar.setValue(0);
    bar.show();

    while (myFile.good())
    {
    myFile >> myStr;
    curr = myFile.tellg();

    // special handling for end of file.

    if (curr != -1)
    percent = curr * 100 / end;
    else
    percent = 100;

    bar.setValue(persent);

    cout << percent << " percent finished." << endl;
    }
    @

    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":https://gitorious.org/spdar/spdar/trees/master, but proceed with caution, as this is a very old and crude code :) Works, though.



  • 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 [[Doc:QProgressDialog]] can be an alternative too, it suffers from a non running event loop too.



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

    @while (something)
    {
    do some work
    update to reflect the progress of the work
    }
    @



  • 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.



  • 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.



  • 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/Threads_Events_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.



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

    bq. 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?



  • 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.



  • 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.



  • 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...

    @#include <iostream>
    #include <fstream>
    #include <string>

    using namespace std;

    #include <QApplication>
    #include <QProgressBar>

    long getFileSize (fstream& f)
    {
    long end;

    f.seekg (0, ios::end);
    end = f.tellg();
    f.seekg(0, ios::beg);

    return end;
    }

    class WorkerThread : public QObject
    {
    Q_OBJECT
    private:
    fstream myFile;
    public:
    WorkerThread() : myFile("test.pro.user") {}

    void run()
    {
    long curr, end, percent;
    string myStr;

    end = getFileSize(myFile);

    while (myFile.good())
    {
    myFile >> myStr;
    curr = myFile.tellg();

    // special handling for end of file.

    if (curr != -1)
    percent = curr * 100 / end;
    else
    percent = 100;

    // some kind of signal to be emitted here

    cout << percent << " percent finished." << endl;
    }
    }
    signals:
    void percentChanged(int percent);
    };

    int main(int argc, char *argv[])
    {
    QApplication app (argc, argv);
    WorkerThread worker;

    QProgressBar bar(0);
    bar.setRange(0, 100);
    bar.setValue(0);
    bar.show();
    // bar.setValue(percent);

    return app.exec();
    }

    void WorkerThread::percentChanged(int percent) {}
    @



  • On a first glance, that looks good. Despite the fact that you might consider using [[Doc:QFile]]/[[Doc:QTextStream]] for the file operations and [[Doc: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:

    @
    // some kind of signal to be emitted here
    emit percentChanged(percent);
    @

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

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

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

    @
    QProgressBar bar(0);
    bar.setRange(0, 100);
    bar.setValue(0);
    bar.show();

    WorkerThread worker;
    QThread thread;
    worker.moveToThread(&thread);

    // run the run method of the worker object once the thread has started
    connect(&thread, SIGNAL(started())), &worker, SLOT(run()));

    // update the progress bar
    connect(&worker, SIGNAL(percentChanged(int)), &bar, SLOT(setValue(int)));

    thread.start();
    @

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



  • [quote author="Volker" date="1329477431"]On a first glance, that looks good. Despite the fact that you might consider using [[Doc:QFile]]/[[Doc:QTextStream]] for the file operations and [[Doc:QFileInfo]] for getting the file size (instead of your method)[/quote]

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

    get this working with a minimum of effort on my sample program

    transfer the Qt-specific code to my simulator

    let the powers-that-be see how cool even a bit of Qt razzle-dazzle is

    go whole-hog on the big job that's hopefully coming up this spring

    [quote]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.[/quote]

    Oh yeah; I knew that (sort of).

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

    @
    // some kind of signal to be emitted here
    emit percentChanged(percent);
    @

    bq. You'll also have to connect that signal to the progress bar:

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

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

    @
    QProgressBar bar(0);
    bar.setRange(0, 100);
    bar.setValue(0);
    bar.show();

    WorkerThread worker;
    QThread thread;
    worker.moveToThread(&thread);

    // run the run method of the worker object once the thread has started
    connect(&thread, SIGNAL(started())), &worker, SLOT(run()));

    // update the progress bar
    connect(&worker, SIGNAL(percentChanged(int)), &bar, SLOT(setValue(int)));

    thread.start();
    @

    bq. to make the autostart work, you must declare WorkerThread::run() as public slot.[/quote]

    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.


  • Moderators

    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()"



  • 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.



  • [quote author="Volker" date="1329495814"]Oh, using QFile and friends is much more easier than the stdlib methods - give it a try! :) [/quote]

    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.

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

    Cool...that's the first time I've seen that done.

    OK...now I'm getting a linker error:

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

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



  • Some missing moc steps.

    I would recommend to put the WokerThread class in a separate .h header file (and probably extract the implementation to a .cpp file).

    moc is usually not run on the file that contains the main method.

    If you have changed your project, do a full rebuild to catch up for all the changes.



  • Oh...excellent! This is very, very encouraging.

    EDIT:

    Actually, let's just get to the real thing here: this is the main routine in my application:

    @#include <iostream>
    #include <string>

    #include "modem.h"

    using namespace std;

    int main ()
    {
    Modem modem;
    bool clockEnable = HIGH;
    bool resetFlag = LOW;

    int32_t rc = 0;
    int32_t i = 0;

    while (rc == 0)
    {
    systemClock.setClock(HIGH);
    rc = modem.cycle(clockEnable, resetFlag);

    systemClock.setClock(LOW);
    modem.cycle(clockEnable, resetFlag); // no need to check rc on low clock

    if (++i % 1000 == 0)
    cout << "main cycled " << dec << i << " times." << endl;
    }

    return 0;
    }
    @

    So...do the guts of this become my run() function? And, what's currently in my example main() essentially goes into my app main()? Is that the gist of it?

    Also, I'd like to move as much of the progress bar processing out of main(). I know it's only three lines now, but...once I get this working in my app, I'm planning on adding some stuff to the UI. Any reason I shouldn't pursue this?

    Thanks...



  • Yes, the contents of the main method would go into the worker class. Make the Modem and the flags a class variable, and the rest into the run slot. The singal and emit is just like in the dummy example we had before.

    [EDIT: the contents of the main method goes to the class, not the method itself, of course :) ]



  • OK, thanks, Volker. On a related subject, I've been experimenting with creating a QMainWindow, but evidently, I don't understand the constructors as well as I thought. The line:

    @ QProgressBar bar(0);@

    Is creating an object of type QProgressBar named bar, and with no parent, right? So why doesn't this:

    @ QMainWindow mainWindow;
    QProgressBar bar(*mainWindow);
    @

    Create a QMainWindow, and then a bar whose parent is the QMainWindow? I'm getting a "no matching function" compiler error.


  • Moderators

    You'd want @QProgessBar bar(&mainWindow)@

    (&foo takes the address of an object, where *foo dereferences a pointer to an object)

    But...

    That's a bad idea! In this case, QProgressBar is created on the stack. Parenting it to QMainWindow would make QMainWindow attempt to take ownership of it (and, as such, try to delete it upon its own destruction, which would cause all kinds of badness to happen) You never want to call delete() on a stack-based item.

    However, the following would be ok:
    @
    QMainWindow mainWindow;
    QProgressBar *bar = new QProgressBar(&mainWindow);
    @



  • mlong is correct here. In general, for real world applications, you should create QWidget based objects on the heap using new. If you set a parent, you do not need to call delete on those, that's handled by Qt automatically.

    Also, you should consider using Qt Designer for creating the UI components. I personally find it much more convenient and easier to use than hard coding that stuff manually.



  • Thank you both for that. So, as a follow-up question...if it's taboo to create bar on the stack, why is it OK to create mainWindow on the stack? (And I realize this is main(), but I see the reasoning in general.)

    And, Volker...I'll go look more closely at Qt Designer for awhile. I assume that whatever I come up with there can be neatly stitched back into my sample application?

    EDIT:

    I just tried the suggestion above:

    @ QMainWindow mainWindow;
    QProgressBar *bar = new QProgressBar(&mainWindow);
    @

    But now the linker is complaining about my second connect() call, which I modified to look like this:

    @ QObject::connect(&worker, SIGNAL(percentChanged(int)), *bar, SLOT(setValue(int)));
    @

    (It's another "no matching function" error; I can provide the text if desired.) What did I do to cause this?



  • Ah, you're trapped by the pointer-reference-address-dereferencing confusion.

    connect takes a pointer for the first and third argument. Which operator (* or & or nothing) you need to prefix the variable with depends on the type of the variable:

    @
    // stack based, w1 is an object
    QWidget w1;

    // pointer to a QWidget
    // & gets the address of w1
    QWidget w1ptr = &w1;

    // reference to a QWidget
    // no special operator is needed for the object w1
    QWidget &w1ref1 = w1;

    // reference to a QWidget
    // dereference the w1ptr with * to get the object
    QWidget &w1ref2 = *w1ptr;

    // call a method:
    w1.objectName();
    w1ptr->objectName();
    w1ref1.objectName();

    // ------------------------------

    // heap based, w2 is a pointer
    QWidget w2 = new QWidget(this);

    // pointer to a QWidget
    // w2 is a pointer - no conversion needed
    QWidget *w2ptr = w2;

    // reference to a QWidget
    // dereference the w1ptr with * to get the object in both cases
    QWidget &w2ref1 = *w2;
    QWidget &w2ref2 = *w2ptr;

    // call a method:
    w2->objectName();
    w2ptr->objectName();
    w2ref1.objectName();
    w2ref2.objectName();
    @

    So, back to connect:

    In our first example, all object were created on the stack, and you had that objects in the variables. To make connect() happy, you need to make them to pointers using operator &.

    In your modified code, you already have a pointer to the progress bar, so you do not need an operator at all:

    @
    QObject::connect(&worker, SIGNAL(percentChanged(int)), bar, SLOT(setValue(int)));
    @

    Passing *bar would be the very same as passing bar (without operator) in the first version.

    I suggest reading some C++ tutorial on that topic. I'm sure they explain this much better than me :-)



  • Hey, Volker...you explained that just fine. That was just a brain-fade on my part. So, back to my earlier question: if bar needs to be on the heap, why doesn't mainWindow?

    Also, as long as I'm throwing out random questions: I notice my program no longer terminates. When I get to the end of file, can I signal a dismissal or something from the worker thread?

    Alternatively, I can create a "quit" button or something; at that point, a little text and this thing is ready to go.

    Thanks.

    EDIT: one more random question: I was experimenting with adding text to the progress bar. (I know I can get it to show the precent completed, but right now, I'm being lazy and just going with "xxx." I'm still cracking the code of the documentation syntax, so I'm not sure how to form this: I tried:
    @ QString text = "xxx";
    bar->text = text;
    @
    But that didn't work (presumably because the text property is private). So I tried:
    @ QString text = "xxx";
    bar->text(text);
    @
    But that didn't work either. The compiler is giving me an error that I'm having trouble interpreting (something about using "const" in the call). What am I doing wrong here?



  • Ah...after looking at the doc a bit more closely, I believe I misunderstood the text() function. It appears to be a getter, not a setter, so I can see how that wouldn't have worked. What's less clear is how I do set the text.

    Here's my calls to the bar object:

    @ bar->setRange(0, 100);
    bar->setValue(0);
    bar->move(10, 10);
    bar->setTextVisible(true);
    bar->show();
    @

    What am I missing here to get the progress text to display? Thanks.



  • The "format property":/doc/qt-4.8/qprogressbar.html#format-prop of QProgressBar holds the text template that is displayed. Be aware that it's possible that the text is not displayed at all, e.g. in the Mac style!

    For your quit problem:
    By default, the application is not terminated once the last window has been closed. You can change this by adding the following line to your main method:

    @
    QApplication app(argc, argv);

    app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
    @

    A quit button does work too, of course.



  • I did look at that (honestly!)...I figured that since it said:

    @The default value is "%p%".@

    ...that would mean it would show up by default. No big deal; it was more for my education anyway.

    Thanks for the info on the quit slot. I do plan on adding a "close" button; just taking on one thing at a time. Right now, I'm fighting a weird Windoze build/debug problem.

    More questions to follow soon...thanks.

    EDIT:

    Regarding the quit slot you suggested above, I now get a rather ominous message in the console window when I close the app window:

    bq. QThread: Destroyed while thread is still running

    Have I left some loose ends here? For example, am I supposed to write my own quit() to overload whatever the one supplied by QApplication does?



  • OK, I'm back to this. I'm ready to add a "close" button. In fact, I've already added the button object using QPushButton. Before I wade into connecting this up with signals and slots, I have a couple housekeeping questions:

    1. I still am not sure I understand why the completion percentage doesn't show up in text format. The way I read the documentation, by default, "%p%" will display, and just to make sure, I added a line:

    @ bar->setFormat("%p%");
    @

    But nothing shows up.

    1. I now have a QMainWindow objects that "contains" (if that's the right term) two other objects: QProgressBar and QPushButton. Each of these objects will need some formatting code, as will (I imagine) any other objects I might add. Currently, I have a routine to configure the progress bar:

    @void configBar(QProgressBar* bar)
    {
    QString text;
    bar->setRange(0, 100);
    bar->setValue(0);
    bar->move(50, 50);
    bar->setTextVisible(true);
    bar->setFormat("%p%");
    bar->show();
    text = bar->text();
    }
    @

    As you can see, I pass it a pointer to the object as a parameter. It wouldn't seem to make sense that I'd have a separate routine for each object; is there some way to derive the "contained" objects from QMainWindow? That way, I could do all my formatting in one routine. Or, should I create my own class, with all of these objects as member elements, and access them that way?

    Thanks.


  • Moderators

    1. The setFormat as written above should set the text to "0%" through "100%" as the progress bar progresses. But, as Volker said, keep in mind that text may or may not be displayed (depending on the widget style in use.) The %p is the percentage, and the trailing % is just a percent sign. %m would be used for the current step, and %v would be used for the actual value.

    2. The widgets in your mainwindow should be declared as private: member variables in the mainwindow header file. (This is basic encapsulation, so I hope I'm not saying anything too obvious to you.) Then you can have one setup routine. Traditionally (though it's a matter of style) I would something such as:

    mainwindow.h:
    @
    class MainWindow : public QMainWindow
    {
    Q_OBJECT
    ...
    void MainWindow(QWidget *parent = 0);
    ...
    private:
    void setupWidgets();

    QProgressBar *m_bar;
    QPushButton *m_button;
    // etc.
    };
    @

    and in the .cpp file:
    @
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
    {
    m_bar = new QProgressBar(this); // these are children of MainWindow
    m_button = new QPushButton(this);

    setupWidgets();
    }
    ...
    void MainWindow::setupWidgets()
    {
    // setup ProgressBar
    m_bar->setRange(0, 100);
    m_bar->setValue(0);
    m_bar->move(50, 50);
    m_bar->setTextVisible(true);
    m_bar->setFormat("%p%");
    // since calling MainWindow::show() will call show() on its children, there's no need to use m_bar->show().

    // setup PushButton
    m_button->setText("Close");

    // can also set up any appropriate signals/slots, etc. here

    }
    @
    (Brain to terminal. For illustrative purposes only.)

    I'm not sure why you're setting the "text" variable in your code above, but if you needed it somewhere else within the MainWindow class, it would be accessable via "m_bar->text()"



  • Thanks for the clarification on #2 above. I was only putting text in the button as a convenience; I'll probably do it as you suggest above.

    I still don't get this:

    bq. But, as Volker said, keep in mind that text may or may not be displayed (depending on the widget style in use.)

    What is this widget style you're talking about? Do I have control over it?

    Thanks.



  • Mlong -

    I've begun re-coding based on your suggestion above. It presents an issue, though. If I create objects (like m_bar) as private members of another object (like mainWindow), then this line of code obviously doesn't work:

    @ QObject::connect(&worker, SIGNAL(percentChanged(int)), bar, SLOT(setValue(int)));
    @

    So, is the solution to define a slot within mainWindow, and somehow re-deliver the signal to m_bar? Or, does Qt offer a somewhat more automated way of accomplishing this?

    Thanks.


  • Moderators

    bq. What is this widget style you’re talking about? Do I have control over it?

    On a Mac, Qt draws widgets in a Mac style. On Windows, it uses Windows' style. And so on. There are a number of ways to override the default style (although not all styles are available on all platforms -- you can't have Mac-styled widgets on Windows, for instance.) See "here":http://developer.qt.nokia.com/doc/qt-4.8/style-reference.html for some more details.

    bq. So, is the solution to define a slot within mainWindow, and somehow re-deliver the signal to m_bar?

    That's probably the most straightforward way I can think of offhand. Just create a public slot in MainWindow that will pass the information on to m_bar. That way you just expose the functionality that the world outside your widget needs to have access to, rather than exposing the entire QProgressBar. It makes the interface to your MainWindow class a little more tidy, in that it can have an interface to the outside world that is directly-related to the business at hand.



  • Thanks for the clarification about styles. It makes sense, now...it explains why Qt applications don't look identical on all platforms.

    I took a stab at changing the signal to go to the mainWindow object. It compiles, but doesn't work:

    in main.cpp, the new connect looks like:

    @ QObject::connect(&worker,
    SIGNAL(percentChanged(int)),
    &mainWindow,
    SLOT(setBarValue(int)));
    @

    and in mainwindow.cpp, I believe I need something like:

    @void MainWindow::setBarValue(int i)
    {
    emit barValueChanged(i);
    }
    @

    But I'm not sure where to go from here. Do I need another connect() call, and/or do I implement another slot to handle this signal, or...not?



  • No, in the slot setBarValue(), you call

    @
    void MainWindow::setBarValue(int i)
    {
    m_bar->setValue(i);
    }
    @



  • 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:

    1. would the connect() call most appropriately go within the MainWindow constructor?
    2. 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.

    1. 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.

    1. 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.


  • Moderators

    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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.