Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Update UI from a C++ library callback
Forum Updated to NodeBB v4.3 + New Features

Update UI from a C++ library callback

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 5 Posters 597 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    SteMMo
    wrote on 6 May 2024, 12:51 last edited by SteMMo 5 Jun 2024, 12:53
    #1

    Hi all,
    I'm writing a QT desktop application that includes a C++ library.
    The library inludes a time consuming procedure that I'd like to see the progression by a progress bar.
    So I wrote a (pure) C++ function that receive a function pointer:

    TerminalProtocol(LOG_DOWNLOAD_CB callback);
    

    where

    typedef void(*LOG_DOWNLOAD_CB)(DownloadLogPhase phase, int nParam);
    

    At the QT side I defined the callback that updates the UI, both listbox and progressbar.
    My current version is:

    void logCallback( DownloadLogPhase phase, int value)
    {
    	QString msg = "?";
    
    	switch(phase)
    	{
    		case DL_PHASE_OK:
    			qDebug() << "[logCallback] OK ";
    			msg = "OK";
    			break;
    		case DL_PHASE_OPENING:
    			qDebug() << "[logCallback] OPENING ";
    			msg = "Opening ..";
    			break;
    		case DL_PHASE_INIT:
    			msg = "Init done!";
    			break;
    		case DL_PHASE_DOWNLOADING:
    			qDebug() << "[logCallback] DOWNLOADING " << value;
    			msg = "Download " + QString::number(value);
    			break;
    		case DL_PHASE_ERROR:
    			qDebug() << "[logCallback] ERROR " << value;	// Codice UpdaterRet_*
    			msg = "ERROR " + QString::number(value) + " - ";
    			msg.append(Updater::GetUpdaterRetDescription((UpdaterRet)value));
    			break;
    		default:
    			qDebug() << "[logCallback] " << phase;
    			msg = "NotHandled phase: " + QString::number(phase);
    	}
    
    	//
    	QWidgetList wl = QApplication::topLevelWidgets();
    	foreach(QWidget * widget, wl) {
    		if (MainWindow* mw = qobject_cast<MainWindow*>(widget)) {
    			mw->ui->listDownloadLog->addItem(msg);
    
    			if (phase == DL_PHASE_DOWNLOADING)
    				mw->ui->progressBar->setValue(value);
    				
    			break;
    		}
    	}
    }
    

    The strange thing is that the 'addItem' call has no problem; the progressbar update generates error:

    ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x1e537a4e4a0. Receiver 'MainWindow' (of type 'MainWindow') was created in thread 0x0x1e52e723c30", file ...
    

    How can I solve this situation?
    Is there other solutions for this architecture (QT app + C++ lib) ??

    Thanks!

    1 Reply Last reply
    0
    • S SamiV123
      6 May 2024, 17:28

      I'd make your callback implementation post a new Event to the Qt event queue.

      you can for example use your dialog/widget/window as the receiver object in your call to QCoreApplication::postEvent(...) call and then you can receive / handle the event in your MyWidget:event(QEvent* event) {} method and there you'd down cast the QEvent to your event type and update the UI appropriately.

      S Offline
      S Offline
      SimonSchroeder
      wrote on 7 May 2024, 06:58 last edited by
      #6

      @SamiV123 said in Update UI from a C++ library callback:

      I'd make your callback implementation post a new Event to the Qt event queue.

      This is the most reasonable approach. Connections of signals/slots between threads just work.

      Another way is to just put everything into a lambda an send that to the GUI thread:

      void logCallback( DownloadLogPhase phase, int value)
      {
      	QString msg = "?";
      
      	switch(phase)
      	{
      		case DL_PHASE_OK:
      			qDebug() << "[logCallback] OK ";
      			msg = "OK";
      			break;
      		case DL_PHASE_OPENING:
      			qDebug() << "[logCallback] OPENING ";
      			msg = "Opening ..";
      			break;
      		case DL_PHASE_INIT:
      			msg = "Init done!";
      			break;
      		case DL_PHASE_DOWNLOADING:
      			qDebug() << "[logCallback] DOWNLOADING " << value;
      			msg = "Download " + QString::number(value);
      			break;
      		case DL_PHASE_ERROR:
      			qDebug() << "[logCallback] ERROR " << value;	// Codice UpdaterRet_*
      			msg = "ERROR " + QString::number(value) + " - ";
      			msg.append(Updater::GetUpdaterRetDescription((UpdaterRet)value));
      			break;
      		default:
      			qDebug() << "[logCallback] " << phase;
      			msg = "NotHandled phase: " + QString::number(phase);
      	}
      
      	QMetaObject::invokeMethod(qGuiApp, [msg=msg]()
              {
      	    QWidgetList wl = QApplication::topLevelWidgets();
           	    foreach(QWidget * widget, wl) {
      		    if (MainWindow* mw = qobject_cast<MainWindow*>(widget)) {
      			    mw->ui->listDownloadLog->addItem(msg);
      
      			    if (phase == DL_PHASE_DOWNLOADING)
      				    mw->ui->progressBar->setValue(value);
      				
      			    break;
      		    }
      	    }
              });
      }
      

      There is a non-zero chance that the order of these calls to the GUI thread is not kept. Have a look at the implementation of guiThread() in my library https://github.com/SimonSchroeder/QtThreadHelper for different ways how to synchronize with the GUI thread. If your progress calls get out of sync using a queue (schedule WorkerThread::ASYNC in my lib) is the best approach. The other two implementations will fully block the calling thread which is most likely not what you want.

      1 Reply Last reply
      3
      • J Offline
        J Offline
        jsulm
        Lifetime Qt Champion
        wrote on 6 May 2024, 12:57 last edited by
        #2

        Apparently your callback is called from another thread than UI thread. Only UI thread is allowed to manupulate UI. You can use QMetaObject::invokeMethod to call setValue in UI thread.

        https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        4
        • S Offline
          S Offline
          SteMMo
          wrote on 6 May 2024, 13:08 last edited by SteMMo 5 Jun 2024, 13:31
          #3

          Thanks, you I forgot to metion that the time consuming procedure runs in a new thread.
          what I should use as 'context' parameter?
          Currently the callback function is defined outside a class: do I need to include in a class?

          Thanks

          M 1 Reply Last reply 6 May 2024, 14:22
          0
          • S SteMMo
            6 May 2024, 13:08

            Thanks, you I forgot to metion that the time consuming procedure runs in a new thread.
            what I should use as 'context' parameter?
            Currently the callback function is defined outside a class: do I need to include in a class?

            Thanks

            M Offline
            M Offline
            mpergand
            wrote on 6 May 2024, 14:22 last edited by mpergand 5 Jun 2024, 14:24
            #4

            @SteMMo said in Update UI from a C++ library callback:

            what I should use as 'context' parameter?

            From your code, it is the address of MainWindow.

            You need to pass a context parameter here:

            TerminalProtocol(LOG_DOWNLOAD_CB callback, void* context);
            

            then in the callback, get back the MainWindow object with a cast:

            void logCallback( DownloadLogPhase phase, int value, void* context)
            {
                MainWindow* mw = qobject_cast<MainWindow*>(context);
            ...
            }
            
            1 Reply Last reply
            1
            • S Offline
              S Offline
              SamiV123
              wrote on 6 May 2024, 17:28 last edited by
              #5

              I'd make your callback implementation post a new Event to the Qt event queue.

              you can for example use your dialog/widget/window as the receiver object in your call to QCoreApplication::postEvent(...) call and then you can receive / handle the event in your MyWidget:event(QEvent* event) {} method and there you'd down cast the QEvent to your event type and update the UI appropriately.

              S 1 Reply Last reply 7 May 2024, 06:58
              3
              • S SamiV123
                6 May 2024, 17:28

                I'd make your callback implementation post a new Event to the Qt event queue.

                you can for example use your dialog/widget/window as the receiver object in your call to QCoreApplication::postEvent(...) call and then you can receive / handle the event in your MyWidget:event(QEvent* event) {} method and there you'd down cast the QEvent to your event type and update the UI appropriately.

                S Offline
                S Offline
                SimonSchroeder
                wrote on 7 May 2024, 06:58 last edited by
                #6

                @SamiV123 said in Update UI from a C++ library callback:

                I'd make your callback implementation post a new Event to the Qt event queue.

                This is the most reasonable approach. Connections of signals/slots between threads just work.

                Another way is to just put everything into a lambda an send that to the GUI thread:

                void logCallback( DownloadLogPhase phase, int value)
                {
                	QString msg = "?";
                
                	switch(phase)
                	{
                		case DL_PHASE_OK:
                			qDebug() << "[logCallback] OK ";
                			msg = "OK";
                			break;
                		case DL_PHASE_OPENING:
                			qDebug() << "[logCallback] OPENING ";
                			msg = "Opening ..";
                			break;
                		case DL_PHASE_INIT:
                			msg = "Init done!";
                			break;
                		case DL_PHASE_DOWNLOADING:
                			qDebug() << "[logCallback] DOWNLOADING " << value;
                			msg = "Download " + QString::number(value);
                			break;
                		case DL_PHASE_ERROR:
                			qDebug() << "[logCallback] ERROR " << value;	// Codice UpdaterRet_*
                			msg = "ERROR " + QString::number(value) + " - ";
                			msg.append(Updater::GetUpdaterRetDescription((UpdaterRet)value));
                			break;
                		default:
                			qDebug() << "[logCallback] " << phase;
                			msg = "NotHandled phase: " + QString::number(phase);
                	}
                
                	QMetaObject::invokeMethod(qGuiApp, [msg=msg]()
                        {
                	    QWidgetList wl = QApplication::topLevelWidgets();
                     	    foreach(QWidget * widget, wl) {
                		    if (MainWindow* mw = qobject_cast<MainWindow*>(widget)) {
                			    mw->ui->listDownloadLog->addItem(msg);
                
                			    if (phase == DL_PHASE_DOWNLOADING)
                				    mw->ui->progressBar->setValue(value);
                				
                			    break;
                		    }
                	    }
                        });
                }
                

                There is a non-zero chance that the order of these calls to the GUI thread is not kept. Have a look at the implementation of guiThread() in my library https://github.com/SimonSchroeder/QtThreadHelper for different ways how to synchronize with the GUI thread. If your progress calls get out of sync using a queue (schedule WorkerThread::ASYNC in my lib) is the best approach. The other two implementations will fully block the calling thread which is most likely not what you want.

                1 Reply Last reply
                3
                • S SteMMo has marked this topic as solved on 14 May 2024, 13:01

                1/6

                6 May 2024, 12:51

                • Login

                • Login or register to search.
                1 out of 6
                • First post
                  1/6
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved