is there any way to use QMessageBox in the other thread???



  • is there any way to use QMessageBox in the other thread???


  • Lifetime Qt Champion

    Hi,

    No, widget related stuff must happen in the GUI thread. The good way to do is to emit a signal from your thread and handle the message box part in your GUI thread.



  • @SGaist
    Hi, Mr.SGaist.
    But if the sub thread needs user to react it's question, and can only continue after user make his or her choice, how can we do that? Still emits a signal and wait for UI's result with a QMutex object? Is there any simpler solution ?



  • @JohnYork Can I ask why you need this information in different thread? Probably you can change your design to avoid this

    BTW you can send a signal to the GUI Thread and this one, after received the User decision, can emit a new signal connected to a thread slot



  • @mcosta
    Hi, Mr(or Mrs) mcosta. Just consider this scene :
    We starts a job with a sub thread, and in the progress we have to ask user to make some simple choice such as select 'yes' or 'no'. In MFC we can simply call a framework API like 'AfxMessageBox', even though we have to pass several parameters to that function, but it makes us no necessary to write code to contact with main thread. In VCL there is also the same way to do.
    In Qt, I like it's simple method to post a message box. And I like it's idea to make things simple. if it could provide the same simple way to post dialog in a sub thread, I think it would be more better!



  • Hi,

    in Qt only the main thread can access to User Interface (sometimes is called GUI thread); there're several good reasons to work in this way.

    A solution could be

    split the job in two step with (for example two slots)

    // Worker.h
    class Worker {
    ...
    signals:
        void step1Finished();
    
    public slots:
        void step1();
        void step2();
        void cancelJob();
    ...
    };
    

    at the end of step1() the worker thread emits step1Finished()

    // Worker.cpp
    void MyThread::step1() 
    {
       ...
       Q_EMIT step1Finished();
    }
    

    In the main thread connect the signal step1Finished() with a slot and in this slot ask to user your question; also create 2 signals and connect them to the worker slots

    // MainWindow.h
    class Worker;
    
    class MainWindow
    {
    ...
    
    signals:
        void goToStep2();
        void cancelJob();
    
    public slots:
        void handleStep1Finished();
    
    ...
    private:
        Worker *_worker;
    };
    
    // MainWindow.cpp
    MainWindow::MainWindow()
    {
    ...
        connect (_worker, &Worker::step1Finished, this, &MainWindow::handleStep1Finished);
        connect (this, &MainWindow::goToStep2, _worker, &Worker::step2);
        connect (this, &MainWindow::cancelJob, _worker, &Worker::cancelJob);
    }
    
    void MainWindow::handleStep1Finished()
    {
        int result = QMessageBox::question (...);
        if (QMessageBox::Yes)
            Q_EMIT goToStep2();
        else
            Q_EMIT cancelJob();
    }
    


  • @mcosta
    Hi, Mr.(Mrs.)mcosta. Thanks for your solution, it's pretty good! Actually, I'm doubted with this feature in Qt. I means : what benefits could provide with this limit?

    After looking over your code, I think we can implement a fake 'QMessageBox' in a thread object by deriving a sub class from QThread :

    class QThreadWithMsgBox : public QThread
    {
        Q_OBJECT
    protected:
        QSemaphore m_uires;
        StandardButton m_btnres;
    public:
        QThreadWithMsgBox(QObject * parent = 0)
            : QThread(parent), m_uires(0), m_btnres(NoButton) {
            connect(this, SIGNAL(information_sig(QWidget *, const QString, const QString, StandardButtons StandardButtons)), SLOT(on_information(QWidget * parent, const QString , const QString, StandardButtons , StandardButtons )));
        }
        
    protected:
        StandardButton information(QWidget * parent, const QString &title, const QString &text, StandardButtons buttons = Ok, StandardButtons defaultButton = NoButton)
        {
            emit information_sig(parent, title, text, buttons, defaultButton);
            m_uires.aquire(1);
            return m_btnres;
        }
    signals:
        void information_sig(QWidget * parent, const QString &title, const QString &text, StandardButtons buttons, StandardButtons defaultButton);
    
    private slots:
        void on_information(QWidget * parent, const QString &title, const QString &text, StandardButtons buttons, StandardButtons defaultButton) {
            m_btnres = QMessageBox::information(parent, title, text, buttons, defaultButton);
            m_uires.release(1);
        }
    }
    

    I'm not sure whether the slots is processed in GUI thread. If it is, I think this class should work well. Or if it isn't, maybe we have to pass a GUI object as the parent of this class, and handle it in the parent.



  • Hi,

    as I said before I don't think you can open a MessageBox in the thread event loop (I didn't tried your code but I think you'll get an error or a warning a runtime).

    what benefits could provide with this limit?

    Allow only one thread to control the UI remove some common problems like:

    • Open Modal dialogs from several threads will block the application;
    • Handle User Input in the right thread;
    • Handle WindowsManager events in the right thread;

    Many frameworks offers some UI lock feature but this creates some issues when you have many threads to manage.
    The Qt approach is: there's only one thread responsible of UI; is you need to change or read some UI controls use signals/slots to use it

    NOTE: I'm a Mr. but you can omit ;)



  • @mcosta
    Hi, Mr.mcosta. Thanks for your explanation and patience. Now I have some basic knowledge about ONE GUI THREAD feature.

    In the tip of MessageBox in thread, I thought your opinion should be right first. But I felt a little regret if I had never test and prove the incorrection. So I wrote a test project just a few minutes ago to test it. And I found it worked ! And without any warning ! I don't know whether there is any potential problem or leak. Would you like to check it ?

    Here is my test project code :

    // mainwindow.h
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QString>
    #include <QMessageBox>
    #include <QThread>
    #include <QSemaphore>
    
    namespace Ui {
    class MainWindow;
    }
    
    // test thread class
    class MsgBoxThread : public QThread
    {
       Q_OBJECT
    protected:
       QSemaphore m_uires;
       int m_btnres;
       int information(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButtons defaultButton = QMessageBox::NoButton)
       {
           emit information_sig(parent, title, text, buttons, defaultButton);
           m_uires.acquire(1);
           return m_btnres;
       }
    
       void run(void) Q_DECL_OVERRIDE {
          information(0,tr("test dialog"),tr("a information from thread."));
       }
    public:
       MsgBoxThread(QObject * parent = 0)
          :QThread(parent), m_uires(0), m_btnres(QMessageBox::NoButton) {
          qRegisterMetaType<QMessageBox::StandardButtons>("QMessageBox::StandardButtons");
          connect(this, SIGNAL(information_sig(QWidget *, const QString, const QString, QMessageBox::StandardButtons, QMessageBox::StandardButtons)), SLOT(on_information(QWidget *, const QString , const QString, QMessageBox::StandardButtons , QMessageBox::StandardButtons )));
       }
    signals:
        void information_sig(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons defaultButton);
    
    private slots:
        void on_information(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons defaultButton) {
            m_btnres = QMessageBox::information(parent, title, text, buttons, defaultButton);
            m_uires.release(1);
        }
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    private slots:
    
        void on_pushButton_2_clicked();
    
    private:
        Ui::MainWindow *ui;
    };
    
    #endif // MAINWINDOW_H
    
    // mainwindow.cpp
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    }
    
    MsgBoxThread test_thread; // instance of the test class
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_pushButton_clicked()
    {
       test_thread.start();
    }
    


  • @JohnYork said:

    void run(void) Q_DECL_OVERRIDE {
    information(0,tr("test dialog"),tr("a information from thread."));
    }

    Your run() doesn't call QThread::exec() so you don't have a thread event loop. In that case the slot is processed in MainThread.
    Here you can find more information



  • @mcosta
    Hi, Mr.mcosta. Thanks for your explanation and documentations you provided.
    Yes, there is no event loop. Our primary goal is to find a simple way to retrieve the user's choice from QMessageBox in a thread. So, opening QMessageBox in MainThread and transfering result into calling thread is just what we want. In this case I think we can partially solve @opengpu2 's problem now.

    On the other hand, after reading document you provided, I found we can omit the use of QSemapher, by changing the connection type of slot to 'Qt::BlockingQueuedConnection' :

    class MsgBoxThread : public QThread
    {
       Q_OBJECT
    protected:
       int m_btnres;
       int information(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButtons defaultButton = QMessageBox::NoButton)
       {
           emit information_sig(parent, title, text, buttons, defaultButton);
           return m_btnres;
       }
    
       void run(void) Q_DECL_OVERRIDE {
          information(0,tr("test dialog"),tr("a information from thread."));
       }
    public:
       MsgBoxThread(QObject * parent = 0)
          :QThread(parent), m_uires(0), m_btnres(QMessageBox::NoButton) {
          qRegisterMetaType<QMessageBox::StandardButtons>("QMessageBox::StandardButtons");
          connect(this, SIGNAL(information_sig(QWidget *, const QString, const QString, QMessageBox::StandardButtons, QMessageBox::StandardButtons)), SLOT(on_information(QWidget *, const QString , const QString, QMessageBox::StandardButtons , QMessageBox::StandardButtons )), Qt::BlockingQueuedConnection);
       }
    signals:
        void information_sig(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons defaultButton);
    
    private slots:
        void on_information(QWidget * parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons defaultButton) {
            m_btnres = QMessageBox::information(parent, title, text, buttons, defaultButton);
        }
    };
    


  • Hi,

    if you don't use a per-thread event loop your message box call is executed in Main Thread context. This is why you don't get errors.


Log in to reply
 

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