Results 1 to 9 of 9

Thread: How to keep the GUI responsive?

  1. #1
    Join Date
    Jul 2008
    Posts
    3
    Qt products
    Qt4
    Platforms
    Windows

    Default How to keep the GUI responsive?

    I'm trying to implement a program, where a lot of computations are done. Of course the GUI is blocked until the computation is done.
    I tried using (boost) threads, but you're not allowed to update widgets (like a progress bar) or replace a QLabel by an image (using QPixmap) from within a seperate (non-GUI) thread.
    Has anyone any idea, how to update a progressbar (which is owned by the main window) and show an image in the mainwindow, which gets available during the computation?

    I already tried to use a QTimer (owned by the main window), but its timeout() event handler was never called.
    Even the following test didn't work:

    Qt Code:
    1. int g_progress = 0;
    2. long int g_count = 1000000000;
    3.  
    4. TestMainWindow::TestMainWindow(QWidget* parent, Qt::WindowFlags flags)
    5. : QMainWindow(parent, flags)
    6. {
    7. // create gui elements defined in the Ui_MainWindow class
    8. setupUi(this);
    9. progressBar->setRange(0, 100);
    10. m_timer = new QTimer();
    11. connect(m_timer, SIGNAL(timeout()), this, SLOT(updateProgress()));
    12. }
    13.  
    14. //is executed on a button click
    15. void TestMainWindow::compute()
    16. {
    17. m_timer->start();
    18. for (int i=0; i < g_count; i++)
    19. {
    20. //progress in percent
    21. g_progress = (int) (i * 100/(float) g_count);
    22. }
    23. m_timer->stop();
    24. }
    25.  
    26. //this method is never called. Why?
    27. void TestMainWindow::updateProgress()
    28. {
    29. progressBar->setValue(g_progress);
    30. }
    To copy to clipboard, switch view to plain text mode 

    TestMainWindow is derived from QMainWindow.

    Is there a better way (using threads) to do heavy computations while keeping the GUI responsive (keep in mind that I want to update the GUI during computation)?

    Wow, that's a long post...

    Thanks in advance!
    Last edited by jpn; 14th July 2008 at 21:31. Reason: missing [code] tags

  2. #2
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: How to keep the GUI responsive?

    You basically have two choices:

    1. using threads
    In this situation you need to divide your computation process into two parts - the computation itself and all related to updating the display. As you noticed the latter can only be done from within the main (aka GUI) thread, so you need to pass the results of the computation from within worker threads to the main thread. In Qt4 there are two safe ways of doing that - one is to use signals and slots and the other is to use custom events. So basically you are doing some computations and when you want to update the display, post a custom event to an object living in the main thread and reimplement its custom event handler ith routines needed to update the display.

    2. not using threads
    The same can be achieved without threads. There are two variants of this solution - a dirty one and a proper one. The dirty one relies on calling QCoreApplication::processEvents() periodically during the computation to allow the application to process its gui events and thus remain responsive. The solution is not clean because you are triggering the event queue in an artificial way and it theory you can still starve the event loop or your computation. The proper way of doing what you want is to divide your computation into small chunks that can be processed separately in a sequence. Then you should connect a timer with a 0 timeout to a slot that will be executing subsequent steps of the computation (or alternatively each chunk will start a single shot timer connected to the next chunk of the computation). When you start the timer and return the flow to the event queue, Qt will take care of the rest by calling the first chunk of your computation. The result will be similar to the processEvents() method but will allow you to pause, cancel or slow down the computation process at each step by manipulating the timers.

  3. #3
    Join Date
    Dec 2006
    Posts
    849
    Thanks
    6
    Thanked 163 Times in 151 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: How to keep the GUI responsive?

    QTimer::timeout() is called in the event loop.
    When a button is clicked, a slot is called from the event loop.
    Execution of the eventloop is suspended until that slot returns
    (unless you start another event loop in the slot or call QEventLoop::processEvents, or QCoreApplication::processEvents() regularly in the slot).

    The better way to do it is threads. It is a bit more complicated, but worth getting one's head around, though.

    HTH

  4. #4
    Join Date
    Apr 2007
    Posts
    23
    Thanks
    6
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: How to keep the GUI responsive?

    I have done exactly what you are asking using QThread.

  5. #5
    Join Date
    Sep 2007
    Location
    Szczecin, Poland
    Posts
    153
    Thanks
    7
    Thanked 11 Times in 8 Posts
    Qt products
    Qt4
    Platforms
    Windows

    Default Re: How to keep the GUI responsive?

    Even thread without executed eventLoop(which is responsible for receiving events) can emit signals.
    Gui can have slot that updates progressBar when receives signal from thread.
    I think it's most common solution,
    at least works fine in mine application.
    See GrEEn (Graphics Effects Environment)
    http://sourceforge.net/project/platf...roup_id=232746
    a qt-based plugins oriented MDI image processing application(contains also qt plugins like styles & imageformats).

  6. #6
    Join Date
    Jul 2008
    Posts
    3
    Qt products
    Qt4
    Platforms
    Windows

    Default Re: How to keep the GUI responsive?

    Thanks a lot for your help. I have now an application with a responsive GUI

    First of all here's what I did:
    I've got a GUI class, named PhotoMosaicMainWindow (derived from QMainWindow), which owns a data object of type/class PhotoMosaicManager (responsible for heavy computations like building a mosaic image, given a database of images and a single motive).
    When the "Compute" button of the main window is pressed, its event handler calls the PhotoMosaicManager's computeMosaic method.
    Now I create a (boost) thread and pass the compute Method as follows:
    Qt Code:
    1. if (m_computationThread != 0)
    2. delete m_computationThread;
    3. m_computationThread = new boost::thread(boost::bind(&(PhotoMosaicManager::computeMosaic), m_mosaicManager));
    To copy to clipboard, switch view to plain text mode 

    In order to access the progress bar (or other parts of the GUI), I emit signals from the PhotoMosaicManager's computeMosaic method. The signals are connected to the corresponding slots in the main window's constructor:

    Qt Code:
    1. PhotoMosaicMainWindow::PhotoMosaicMainWindow(QWidget* parent, Qt::WindowFlags flags)
    2. : QMainWindow(parent, flags)
    3. {
    4. // create gui elements defined in the Ui_MainWindow class
    5. setupUi(this);
    6. progressBar->setRange(0, 100);
    7. m_mosaicManager = new PhotoMosaicManager();
    8. m_mosaicManager->setManagerListener(this);
    9. m_computationThread = 0;
    10. connect(m_mosaicManager, SIGNAL(updateProgress(int)), this, SLOT(updateProgress(int)));
    11. connect(m_mosaicManager, SIGNAL(computationFinished()), this, SLOT(finishedComputation()));
    12. }
    To copy to clipboard, switch view to plain text mode 

    Three questions:
    1) In http://lists.trolltech.com/qt-intere...ad00803-0.html I've read the following warning:
    "Warning: Slots that generate window system events or use window system
    functions must not be connected to a signal that is emitted from a thread
    that is not the GUI thread."
    But this is exactly what I'm doing, isn't it? I've got a slot which updates the progressbar, where the corresponding signal is emitted from a non-GUI thread. Is this a problem?
    2) I would like to design the software in such a way, that all Qt stuff is encapsulated and completely separated from the data and computation classes like PhotoMosaicManager. But in order to emit signals from PhotoMosaicManager's computeMosaic method, I had to derive this class from QObject and use Qt's "emit" syntax. Any suggestions, how I can separate Qt/GUI from data classes? Add some kind of "third layer" between GUI and computation, which is responsible for communication between these two (the third layer could emit the signals)?
    3) corresponding the timer problem, I've had earlier (let me know, if I've got the point):
    my timer-event-handler wasn't called, because I started the timer from within an event handler of a button click (that's why the event loop of the GUI was disrupted) and because I started the heavy computation within this button-event handler immediately after calling timer->start(). Until timer->stop was called at the end of the button-event-handler, the heavy computation was done, so control was never returned back to the GUI event loop during computation and the timer-event could never be triggered...?

  7. #7
    Join Date
    Dec 2006
    Posts
    849
    Thanks
    6
    Thanked 163 Times in 151 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: How to keep the GUI responsive?

    1) cross thread signals are safe: they are not executed 'right now' like normal signals, but rather queued and executed later (in the context of the thread the slots's object is in)
    2) you can use any callback mechanism in your PhotoMosaicManager; e.g. a Boost.Function, or a simple function pointer. Just pass it a function that internally connects to your Qt stuff. That way PhotoMosaicManager does not interact with Qt (directly).
    3) yes, I think you did get that

    HTH

  8. #8
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: How to keep the GUI responsive?

    Quote Originally Posted by drjones View Post
    1) In http://lists.trolltech.com/qt-intere...ad00803-0.html I've read the following warning:
    "Warning: Slots that generate window system events or use window system
    functions must not be connected to a signal that is emitted from a thread
    that is not the GUI thread."
    But this is exactly what I'm doing, isn't it? I've got a slot which updates the progressbar, where the corresponding signal is emitted from a non-GUI thread. Is this a problem?
    The message concerns Qt3 and not Qt4. It was not safe to use signals across threads in Qt3. In Qt4 you may do that but if you are using boost threads, I would suggest forcing queued connections instead of automatic onces in the connect() statements.

    Currently your object lives in the main thread, so signals are emitted as regular function calls and that may cause you some trouble.

    2) I would like to design the software in such a way, that all Qt stuff is encapsulated and completely separated from the data and computation classes like PhotoMosaicManager. But in order to emit signals from PhotoMosaicManager's computeMosaic method, I had to derive this class from QObject and use Qt's "emit" syntax. Any suggestions, how I can separate Qt/GUI from data classes? Add some kind of "third layer" between GUI and computation, which is responsible for communication between these two (the third layer could emit the signals)?
    Provide an abstract interface your "plugins" would implement so that it is possible to call them from an external source and also an abstract interface your "plugin" can call to contact the main application. Implementation of those interfaces can but don't have to contain Qt code.

    3) corresponding the timer problem, I've had earlier (let me know, if I've got the point):
    my timer-event-handler wasn't called, because I started the timer from within an event handler of a button click (that's why the event loop of the GUI was disrupted) and because I started the heavy computation within this button-event handler immediately after calling timer->start(). Until timer->stop was called at the end of the button-event-handler, the heavy computation was done, so control was never returned back to the GUI event loop during computation and the timer-event could never be triggered...?
    Yes, that's correct. Timers are checked at the end of event processing routine, so when that's never called, your timers can't timeout.

  9. #9
    Join Date
    Jul 2008
    Posts
    3
    Qt products
    Qt4
    Platforms
    Windows

    Default Re: How to keep the GUI responsive?

    Thanks again.
    I added notifyComputationFinished() and notifyProgressChanged(int progress) to the abstract ManagerListener class, which is implemented by PhotoMosaicMainWindow.

    Qt Code:
    1. class ManagerListener
    2. {
    3. public:
    4. virtual bool wasComputationCancelled() = 0;
    5. virtual void notifyComputationFinished() = 0;
    6. virtual void notifyProgressChanged(int progress) = 0;
    7. };
    To copy to clipboard, switch view to plain text mode 

    In both methods I emit signals which are connected to slots in the same class:

    Qt Code:
    1. connect(this, SIGNAL(computationFinishedSignal()), this, SLOT(finishedComputation()) /*, Qt::QueuedConnection*/);
    2. connect(this, SIGNAL(progressChangedSignal(int)), this, SLOT(updateProgress(int)) /*, Qt::QueuedConnection*/);
    To copy to clipboard, switch view to plain text mode 

    The PhotoMosaicManager object calls the notify* methods from its computeMosaic method (via an object of type ManagerListener).

    It seems a bit weird, that the PhotoMosaicMainWindow object sends signals to itself, which it will handle then, too. But this is necessary to assure thread safety (GUI update during heavy computation).
    Nevertheless it works, thanks for your help!
    Is this solution the one wysota has proposed?

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.