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. QMessageBox::warning on secondary thread
Forum Updated to NodeBB v4.3 + New Features

QMessageBox::warning on secondary thread

Scheduled Pinned Locked Moved Solved General and Desktop
16 Posts 7 Posters 1.6k Views 2 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.
  • PerdrixP Offline
    PerdrixP Offline
    Perdrix
    wrote on last edited by
    #1

    Some code that's executing in a future created by std::async (so not on the main UI thread) is checking for the existence of a file it's about to read or for an error reading the file it it does exist.

    In either case it is calling QMessageBox::warning() on that thread and the application hangs at that point with the message box displayed without text or buttons.

    I thought that QMessageBox ran its own message pump, and therefore this wouldn't be a problem, but clearly I was wrong.

    How to handle this?

    J.HilkJ 1 Reply Last reply
    0
    • J.HilkJ J.Hilk

      @Perdrix AFAIK yes,

      maybe you also need
      https://doc.qt.io/qt-6/qmetatype.html#Q_DECLARE_METATYPE

      I'm never 100% sure with this. The Meta System is always a lot of magic imho :D

      PerdrixP Offline
      PerdrixP Offline
      Perdrix
      wrote on last edited by
      #14

      Final code apart from the registration on QMessageBox::Icon in main().

      	if (fs::status(filePath).type() != fs::file_type::regular)
      	{
      		QString errorMessage{ QCoreApplication::translate(
      			"DSS::StackingDlg",
      			"%1 does not exist or is not a file").arg(QString::fromStdWString(fileName)) };
      #if defined(_CONSOLE)
      		std::cerr << errorMessage.toUtf8().constData();
      #else
      		QMainWindow* mainWindow{ getMainApplicationWindow() };
      
      		DeepSkyStacker* dss = dynamic_cast<DeepSkyStacker*>(mainWindow);
      		if (nullptr != dss)
      		{
      			bool result = QMetaObject::invokeMethod(dss, "displayMessageBox", Qt::QueuedConnection,
      				Q_ARG(const QString&, errorMessage),
      				Q_ARG(QMessageBox::Icon, QMessageBox::Warning));
      		}
      		else    // This is here for DeepSkyStackerLive which is not yet Qt 
      		{
      			AfxMessageBox(errorMessage.toStdWString().c_str(), MB_OK | MB_ICONWARNING);
      		}
      		
      #endif
      		return false;
      	}
      
      
      1 Reply Last reply
      1
      • PerdrixP Perdrix

        Some code that's executing in a future created by std::async (so not on the main UI thread) is checking for the existence of a file it's about to read or for an error reading the file it it does exist.

        In either case it is calling QMessageBox::warning() on that thread and the application hangs at that point with the message box displayed without text or buttons.

        I thought that QMessageBox ran its own message pump, and therefore this wouldn't be a problem, but clearly I was wrong.

        How to handle this?

        J.HilkJ Online
        J.HilkJ Online
        J.Hilk
        Moderators
        wrote on last edited by
        #2

        @Perdrix By not calling GUI elements, that includes QMessageBox, from any thread but the one where QApplication lives in!

        Simply emit a signal from your thread, and do the Gui stuff in a connected Slot


        Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


        Q: What's that?
        A: It's blue light.
        Q: What does it do?
        A: It turns blue.

        PerdrixP 1 Reply Last reply
        2
        • J.HilkJ J.Hilk

          @Perdrix By not calling GUI elements, that includes QMessageBox, from any thread but the one where QApplication lives in!

          Simply emit a signal from your thread, and do the Gui stuff in a connected Slot

          PerdrixP Offline
          PerdrixP Offline
          Perdrix
          wrote on last edited by
          #3

          @J-Hilk Ok, I've defined a slot in my QMainWindow class:

          void DeepSkyStacker::displayMessageBox(const QString& message, QMessageBox::Icon icon)
          {
          QMessageBox msgBox{ icon, "DeepSkyStacker", message, QMessageBox::Ok , this};
          msgBox.exec();
          }

          The code I want to invoke it from isn't in a Q_OBJECT - in fact it's a free-standing function that's not part of a class. I believe that means I can't emit a signal to display the message.

          I suspect some incantation involving QMetaObject::invokeMethod() is needed?

          If you could help with that, it would be much appreciated.

          Thanks
          David

          J.HilkJ 2 Replies Last reply
          0
          • PerdrixP Perdrix

            @J-Hilk Ok, I've defined a slot in my QMainWindow class:

            void DeepSkyStacker::displayMessageBox(const QString& message, QMessageBox::Icon icon)
            {
            QMessageBox msgBox{ icon, "DeepSkyStacker", message, QMessageBox::Ok , this};
            msgBox.exec();
            }

            The code I want to invoke it from isn't in a Q_OBJECT - in fact it's a free-standing function that's not part of a class. I believe that means I can't emit a signal to display the message.

            I suspect some incantation involving QMetaObject::invokeMethod() is needed?

            If you could help with that, it would be much appreciated.

            Thanks
            David

            J.HilkJ Online
            J.HilkJ Online
            J.Hilk
            Moderators
            wrote on last edited by
            #4

            @Perdrix said in QMessageBox::warning on secondary thread:

            If you could help with that, it would be much appreciated.

            depends, how exactly are you invoke that free-standing function in a different thread ?

            can you show that code part ?


            Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


            Q: What's that?
            A: It's blue light.
            Q: What does it do?
            A: It turns blue.

            PerdrixP 1 Reply Last reply
            0
            • PerdrixP Perdrix

              @J-Hilk Ok, I've defined a slot in my QMainWindow class:

              void DeepSkyStacker::displayMessageBox(const QString& message, QMessageBox::Icon icon)
              {
              QMessageBox msgBox{ icon, "DeepSkyStacker", message, QMessageBox::Ok , this};
              msgBox.exec();
              }

              The code I want to invoke it from isn't in a Q_OBJECT - in fact it's a free-standing function that's not part of a class. I believe that means I can't emit a signal to display the message.

              I suspect some incantation involving QMetaObject::invokeMethod() is needed?

              If you could help with that, it would be much appreciated.

              Thanks
              David

              J.HilkJ Online
              J.HilkJ Online
              J.Hilk
              Moderators
              wrote on last edited by
              #5

              @Perdrix Actually, std::async doesn't require a free function to invoke.

              have you considered creating a temporary parent QObject class to invoke that method ?

              would be the easiest I think


              Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


              Q: What's that?
              A: It's blue light.
              Q: What does it do?
              A: It turns blue.

              1 Reply Last reply
              0
              • J.HilkJ J.Hilk

                @Perdrix said in QMessageBox::warning on secondary thread:

                If you could help with that, it would be much appreciated.

                depends, how exactly are you invoke that free-standing function in a different thread ?

                can you show that code part ?

                PerdrixP Offline
                PerdrixP Offline
                Perdrix
                wrote on last edited by
                #6

                @J-Hilk

                					const auto readTask = [this, pStackingInfo, firstBitmap = m_vBitmaps.cbegin()](const size_t lightTaskNdx, ProgressBase* pProgress) -> std::pair<std::shared_ptr<CMemoryBitmap>, int>
                					{
                						if (lightTaskNdx >= pStackingInfo->m_pLightTask->m_vBitmaps.size())
                							return { {}, -1 };
                						const int bitmapNdx = FindBitmapIndex(pStackingInfo->m_pLightTask->m_vBitmaps[lightTaskNdx].filePath.c_str());
                						if (bitmapNdx < 0)
                							return { {}, -1 };
                						const auto& lightframeInfo = m_vBitmaps[bitmapNdx];
                						if (lightframeInfo.m_bDisabled)
                							return { {}, -1 };
                
                						ZTRACE_RUNTIME("Stack %s", lightframeInfo.filePath.generic_string().c_str());
                
                						std::shared_ptr<CMemoryBitmap> rpBitmap;
                						if (::LoadFrame(lightframeInfo.filePath, PICTURETYPE_LIGHTFRAME, pProgress, rpBitmap))
                							return { rpBitmap, bitmapNdx };
                						else
                							return { {}, -1 };
                					};
                
                					auto futureForRead = std::async(std::launch::deferred, readTask, 0, m_pProgress); // Load first lightframe synchronously.
                					const auto firstBitmap = m_vBitmaps.cbegin();
                
                					for (size_t i = 0; i < pStackingInfo->m_pLightTask->m_vBitmaps.size() && !bStop; ++i)
                					{
                						auto [pBitmap, bitmapNdx] = futureForRead.get();
                						futureForRead = std::async(std::launch::async, readTask, i + 1, nullptr); // Immediately load next lightframe asynchronously (need to set progress pointer to null).
                
                

                loadFrame() in turn invokes FetchPicture() after doing some stuff.

                In FetchPicture() I wrote this:

                	if (fs::status(filePath).type() != fs::file_type::regular)
                	{
                		QString errorMessage{ QCoreApplication::translate(
                			"DSS::StackingDlg",
                			"%1 does not exist or is not a file").arg(QString::fromStdWString(fileName)) };
                #if defined(_CONSOLE)
                		std::cerr << errorMessage.toUtf8().constData();
                #else
                		QMainWindow* mainWindow{ getMainApplicationWindow() };
                		ZASSERT(nullptr != mainWindow);
                		QMetaObject::invokeMethod(mainWindow,
                			SLOT(displayMessageBox(const QString&, QMessageBox::Icon)),
                			Q_ARG(QString, errorMessage),
                			Q_ARG(QMessageBox::Icon, QMessageBox::Warning));
                		
                #endif
                		return false;
                	}
                

                Unfortunately the displayMessageBox() mf of the DeepSkyStacker class never gets driven?

                jsulmJ J.HilkJ 2 Replies Last reply
                0
                • PerdrixP Perdrix

                  @J-Hilk

                  					const auto readTask = [this, pStackingInfo, firstBitmap = m_vBitmaps.cbegin()](const size_t lightTaskNdx, ProgressBase* pProgress) -> std::pair<std::shared_ptr<CMemoryBitmap>, int>
                  					{
                  						if (lightTaskNdx >= pStackingInfo->m_pLightTask->m_vBitmaps.size())
                  							return { {}, -1 };
                  						const int bitmapNdx = FindBitmapIndex(pStackingInfo->m_pLightTask->m_vBitmaps[lightTaskNdx].filePath.c_str());
                  						if (bitmapNdx < 0)
                  							return { {}, -1 };
                  						const auto& lightframeInfo = m_vBitmaps[bitmapNdx];
                  						if (lightframeInfo.m_bDisabled)
                  							return { {}, -1 };
                  
                  						ZTRACE_RUNTIME("Stack %s", lightframeInfo.filePath.generic_string().c_str());
                  
                  						std::shared_ptr<CMemoryBitmap> rpBitmap;
                  						if (::LoadFrame(lightframeInfo.filePath, PICTURETYPE_LIGHTFRAME, pProgress, rpBitmap))
                  							return { rpBitmap, bitmapNdx };
                  						else
                  							return { {}, -1 };
                  					};
                  
                  					auto futureForRead = std::async(std::launch::deferred, readTask, 0, m_pProgress); // Load first lightframe synchronously.
                  					const auto firstBitmap = m_vBitmaps.cbegin();
                  
                  					for (size_t i = 0; i < pStackingInfo->m_pLightTask->m_vBitmaps.size() && !bStop; ++i)
                  					{
                  						auto [pBitmap, bitmapNdx] = futureForRead.get();
                  						futureForRead = std::async(std::launch::async, readTask, i + 1, nullptr); // Immediately load next lightframe asynchronously (need to set progress pointer to null).
                  
                  

                  loadFrame() in turn invokes FetchPicture() after doing some stuff.

                  In FetchPicture() I wrote this:

                  	if (fs::status(filePath).type() != fs::file_type::regular)
                  	{
                  		QString errorMessage{ QCoreApplication::translate(
                  			"DSS::StackingDlg",
                  			"%1 does not exist or is not a file").arg(QString::fromStdWString(fileName)) };
                  #if defined(_CONSOLE)
                  		std::cerr << errorMessage.toUtf8().constData();
                  #else
                  		QMainWindow* mainWindow{ getMainApplicationWindow() };
                  		ZASSERT(nullptr != mainWindow);
                  		QMetaObject::invokeMethod(mainWindow,
                  			SLOT(displayMessageBox(const QString&, QMessageBox::Icon)),
                  			Q_ARG(QString, errorMessage),
                  			Q_ARG(QMessageBox::Icon, QMessageBox::Warning));
                  		
                  #endif
                  		return false;
                  	}
                  

                  Unfortunately the displayMessageBox() mf of the DeepSkyStacker class never gets driven?

                  jsulmJ Offline
                  jsulmJ Offline
                  jsulm
                  Lifetime Qt Champion
                  wrote on last edited by jsulm
                  #7

                  @Perdrix said in QMessageBox::warning on secondary thread:

                  displayMessageBox

                  QMainWindow does not have such a slot, it is your own, right?
                  I think you need to cast QMainWindow* to your main window class pointer.

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

                  1 Reply Last reply
                  1
                  • PerdrixP Perdrix

                    @J-Hilk

                    					const auto readTask = [this, pStackingInfo, firstBitmap = m_vBitmaps.cbegin()](const size_t lightTaskNdx, ProgressBase* pProgress) -> std::pair<std::shared_ptr<CMemoryBitmap>, int>
                    					{
                    						if (lightTaskNdx >= pStackingInfo->m_pLightTask->m_vBitmaps.size())
                    							return { {}, -1 };
                    						const int bitmapNdx = FindBitmapIndex(pStackingInfo->m_pLightTask->m_vBitmaps[lightTaskNdx].filePath.c_str());
                    						if (bitmapNdx < 0)
                    							return { {}, -1 };
                    						const auto& lightframeInfo = m_vBitmaps[bitmapNdx];
                    						if (lightframeInfo.m_bDisabled)
                    							return { {}, -1 };
                    
                    						ZTRACE_RUNTIME("Stack %s", lightframeInfo.filePath.generic_string().c_str());
                    
                    						std::shared_ptr<CMemoryBitmap> rpBitmap;
                    						if (::LoadFrame(lightframeInfo.filePath, PICTURETYPE_LIGHTFRAME, pProgress, rpBitmap))
                    							return { rpBitmap, bitmapNdx };
                    						else
                    							return { {}, -1 };
                    					};
                    
                    					auto futureForRead = std::async(std::launch::deferred, readTask, 0, m_pProgress); // Load first lightframe synchronously.
                    					const auto firstBitmap = m_vBitmaps.cbegin();
                    
                    					for (size_t i = 0; i < pStackingInfo->m_pLightTask->m_vBitmaps.size() && !bStop; ++i)
                    					{
                    						auto [pBitmap, bitmapNdx] = futureForRead.get();
                    						futureForRead = std::async(std::launch::async, readTask, i + 1, nullptr); // Immediately load next lightframe asynchronously (need to set progress pointer to null).
                    
                    

                    loadFrame() in turn invokes FetchPicture() after doing some stuff.

                    In FetchPicture() I wrote this:

                    	if (fs::status(filePath).type() != fs::file_type::regular)
                    	{
                    		QString errorMessage{ QCoreApplication::translate(
                    			"DSS::StackingDlg",
                    			"%1 does not exist or is not a file").arg(QString::fromStdWString(fileName)) };
                    #if defined(_CONSOLE)
                    		std::cerr << errorMessage.toUtf8().constData();
                    #else
                    		QMainWindow* mainWindow{ getMainApplicationWindow() };
                    		ZASSERT(nullptr != mainWindow);
                    		QMetaObject::invokeMethod(mainWindow,
                    			SLOT(displayMessageBox(const QString&, QMessageBox::Icon)),
                    			Q_ARG(QString, errorMessage),
                    			Q_ARG(QMessageBox::Icon, QMessageBox::Warning));
                    		
                    #endif
                    		return false;
                    	}
                    

                    Unfortunately the displayMessageBox() mf of the DeepSkyStacker class never gets driven?

                    J.HilkJ Online
                    J.HilkJ Online
                    J.Hilk
                    Moderators
                    wrote on last edited by J.Hilk
                    #8

                    @Perdrix ah ok, thats a way to do it :D

                    since this connect is a cross threads, the arguments need to be copied over. For QString this is trivial and done out of the box for you.

                    But I think you need to register enum QMessageBox::Icon with the meta system so it knows what it is and can copy it over

                    https://doc.qt.io/qt-6/qmetatype.html#qRegisterMetaType

                    Edit:
                    the Point of @jsulm is also more than valid! You'll need to cast it to your actual class, that has the slot :D


                    Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                    Q: What's that?
                    A: It's blue light.
                    Q: What does it do?
                    A: It turns blue.

                    Christian EhrlicherC PerdrixP 2 Replies Last reply
                    0
                    • J.HilkJ J.Hilk

                      @Perdrix ah ok, thats a way to do it :D

                      since this connect is a cross threads, the arguments need to be copied over. For QString this is trivial and done out of the box for you.

                      But I think you need to register enum QMessageBox::Icon with the meta system so it knows what it is and can copy it over

                      https://doc.qt.io/qt-6/qmetatype.html#qRegisterMetaType

                      Edit:
                      the Point of @jsulm is also more than valid! You'll need to cast it to your actual class, that has the slot :D

                      Christian EhrlicherC Offline
                      Christian EhrlicherC Offline
                      Christian Ehrlicher
                      Lifetime Qt Champion
                      wrote on last edited by
                      #9

                      Another way would be to use a custom QEvent and simply post it to the global event queue. This way you don't need to know who is the receiver. Good for e.g. status messages from anywhere inside your app to show it in a central widget/status bar.

                      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                      Visit the Qt Academy at https://academy.qt.io/catalog

                      JonBJ 1 Reply Last reply
                      2
                      • J.HilkJ J.Hilk

                        @Perdrix ah ok, thats a way to do it :D

                        since this connect is a cross threads, the arguments need to be copied over. For QString this is trivial and done out of the box for you.

                        But I think you need to register enum QMessageBox::Icon with the meta system so it knows what it is and can copy it over

                        https://doc.qt.io/qt-6/qmetatype.html#qRegisterMetaType

                        Edit:
                        the Point of @jsulm is also more than valid! You'll need to cast it to your actual class, that has the slot :D

                        PerdrixP Offline
                        PerdrixP Offline
                        Perdrix
                        wrote on last edited by
                        #10

                        @J-Hilk So to register QMessageBox::Icon

                        Is that as simple as adding this to main()

                        	//
                        	// Register the QMessageBox::Icon enum as a meta type
                        	//
                        	qRegisterMetaType<QMessageBox::Icon>();
                        
                        JonBJ J.HilkJ 2 Replies Last reply
                        0
                        • Christian EhrlicherC Christian Ehrlicher

                          Another way would be to use a custom QEvent and simply post it to the global event queue. This way you don't need to know who is the receiver. Good for e.g. status messages from anywhere inside your app to show it in a central widget/status bar.

                          JonBJ Offline
                          JonBJ Offline
                          JonB
                          wrote on last edited by
                          #11

                          @Christian-Ehrlicher
                          That is a useful technique, compared to having to create/maintain a QObject. Shame it's not documented somewhere as a useful approach.

                          1 Reply Last reply
                          0
                          • PerdrixP Perdrix

                            @J-Hilk So to register QMessageBox::Icon

                            Is that as simple as adding this to main()

                            	//
                            	// Register the QMessageBox::Icon enum as a meta type
                            	//
                            	qRegisterMetaType<QMessageBox::Icon>();
                            
                            JonBJ Offline
                            JonBJ Offline
                            JonB
                            wrote on last edited by JonB
                            #12

                            @Perdrix
                            May I mention that you can avoid registration of types if you provided a MainWindow::displayWarningBox() method (which can call displayMessageBox() itself) which did not need a QMessageBox::Icon sent to it. This would allow the caller more of an "abstraction" layer which does not need to know so many Qt/message box details.

                            1 Reply Last reply
                            1
                            • PerdrixP Perdrix

                              @J-Hilk So to register QMessageBox::Icon

                              Is that as simple as adding this to main()

                              	//
                              	// Register the QMessageBox::Icon enum as a meta type
                              	//
                              	qRegisterMetaType<QMessageBox::Icon>();
                              
                              J.HilkJ Online
                              J.HilkJ Online
                              J.Hilk
                              Moderators
                              wrote on last edited by
                              #13

                              @Perdrix AFAIK yes,

                              maybe you also need
                              https://doc.qt.io/qt-6/qmetatype.html#Q_DECLARE_METATYPE

                              I'm never 100% sure with this. The Meta System is always a lot of magic imho :D


                              Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                              Q: What's that?
                              A: It's blue light.
                              Q: What does it do?
                              A: It turns blue.

                              PerdrixP 1 Reply Last reply
                              0
                              • J.HilkJ J.Hilk

                                @Perdrix AFAIK yes,

                                maybe you also need
                                https://doc.qt.io/qt-6/qmetatype.html#Q_DECLARE_METATYPE

                                I'm never 100% sure with this. The Meta System is always a lot of magic imho :D

                                PerdrixP Offline
                                PerdrixP Offline
                                Perdrix
                                wrote on last edited by
                                #14

                                Final code apart from the registration on QMessageBox::Icon in main().

                                	if (fs::status(filePath).type() != fs::file_type::regular)
                                	{
                                		QString errorMessage{ QCoreApplication::translate(
                                			"DSS::StackingDlg",
                                			"%1 does not exist or is not a file").arg(QString::fromStdWString(fileName)) };
                                #if defined(_CONSOLE)
                                		std::cerr << errorMessage.toUtf8().constData();
                                #else
                                		QMainWindow* mainWindow{ getMainApplicationWindow() };
                                
                                		DeepSkyStacker* dss = dynamic_cast<DeepSkyStacker*>(mainWindow);
                                		if (nullptr != dss)
                                		{
                                			bool result = QMetaObject::invokeMethod(dss, "displayMessageBox", Qt::QueuedConnection,
                                				Q_ARG(const QString&, errorMessage),
                                				Q_ARG(QMessageBox::Icon, QMessageBox::Warning));
                                		}
                                		else    // This is here for DeepSkyStackerLive which is not yet Qt 
                                		{
                                			AfxMessageBox(errorMessage.toStdWString().c_str(), MB_OK | MB_ICONWARNING);
                                		}
                                		
                                #endif
                                		return false;
                                	}
                                
                                
                                1 Reply Last reply
                                1
                                • PerdrixP Perdrix has marked this topic as solved on
                                • S Offline
                                  S Offline
                                  SimonSchroeder
                                  wrote on last edited by
                                  #15

                                  Your approach is good (and working). You can still avoid some hassle by using lambdas instead. Here is what we basically do in our software:

                                  QMetaObject::invokeMethod(qApp, [&errorMessage](){ QMessageBox{icon, "DeepSkyStacker", errorMessage}.exec(); });
                                  

                                  Please note that your thread will just continue running before the message box might even be shown. Sometimes you might want to wait for a response by the user. For this we need extra synchronization. I've put several use case into a nice little header-only library: https://github.com/SimonSchroeder/QtThreadHelper

                                  You can have a look how you might solve some of the problems.

                                  1 Reply Last reply
                                  1
                                  • B Offline
                                    B Offline
                                    Borbixxx
                                    wrote on last edited by Borbixxx
                                    #16
                                    This post is deleted!
                                    1 Reply Last reply
                                    0

                                    • Login

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