简体   繁体   中英

Create a QDialog in main thread

I'm very new at Qt and I'm having issues when creating a new dialog. Here is its class:

class MyDialog : public QDialog, public Ui::ConnectToSource
{
public:
    MyDialog(QMainWindow *p_Parent = 0);
    void keyPressEvent(QKeyEvent* e);
};

MyDialog::MyDialog(QMainWindow *p_Parent) : QDialog(p_Parent) {
    setupUi(this);
}

In another thread rather than the main one, I'm doing this:

m_Dialog = new MyDialog(parent);

But It's saying that I can't create a new widget in another thread. So what I tried:

void MyQObject::initialize_m_Dialog(QMainWindow* p) { //slot
    m_Dialog = new MyDialog(p);
}
...
QMetaObject::invokeMethod(this, "initialize_m_Dialog", Qt::BlockingQueuedConnection, Q_ARG(QMainWindow*, parent));

But I'm not really sure what I'm doing... :) I'm getting a dead lock with this.

How can I achieve this?

The design of the existing code is rotten. Let's fix it, starting with the dialog.

The Ui:: classes should not be public bases. They are an implementation detail that should never be exposed anywhere outside of the dialog. The dialog is an abstraction: you can perform some operations on it. They should be methods, and access the Ui:: object internally. The dialog should also not depend on any particular type of the parent window. If the dialog should interact with some other objects, it should emit signals that then get connected eg to the main window.

To demonstrate it, suppose that the dialog has a QLineEdit edit element. The text can be set, and others informed of changes in the text. It should be designed as follows:

class ConnectToSource : public QDialog {
  Q_OBJECT
public:
  ConnectToSource(QWidget *parent = {}) : QDialog(parent) {
    ui.setupUi(this);
    connect(ui.edit, &QLineEdit::textChanged,
            this, &ConnectToSource::textChanged); // forward the signal
  }
  Q_SLOT void setText(const QString & text) {
    ui.edit->setText(text);
  }
  QString text() const {
    return ui.edit->text();
  }
  Q_SIGNAL void textChanged(const QString &);
protected:
  void keyPressEvent(QKeyEvent *) override { ... }
private:
  Ui::ConnectToSource ui;
};  

Now, let's look how we might access it from any thread. The key is to send some piece of code to execute in the main thread. See this answer for details. That piece of code - a functor - should carry all the data necessary to set up the dialog.

Then:

// https://stackoverflow.com/a/21653558/1329652
template <typename F>
static void postToThread(F && fun, QThread * thread = qApp->thread());

void setupDialog(MainWindow *parent, const QString &text) {
  postToThread([=]{ // the functor captures parent and text by value
    auto dialog = new ConnectToSource(parent);
    dialog->setText(text);
    connect(dialog, &ConnectToSource::textChanged, parent, &MainWindow::useText);
    dialog->show();
    dialog->setAttribute(Qt::WA_DeleteOnClose); // don't leak the dialog
  });
}

The setupDialog function is thread-safe, and can be executed in any thread as long as the thread doesn't outlive the parent .

Note that the above code is essentially non-blocking. The functor is wrapped up in an event and is delivered to the main thread's event dispatcher, which then executes the functor. The thread executing setupDialog may only get contented on the mutex to the main thread's event queue. This mutex is held only sporadically, for a very short time.

I created a demo for UI component creation in the different thread in below, MyThread will emit a signal after it started. if the UI thread receives the signal will create and show the dialog .

MyThread:

#include <QThread>
#include <QDebug>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject* parent = 0) :
        QThread(parent){}
protected:
    void run(){
        qDebug()<<"Current thread:"<<QThread::currentThread();
        emit somethingHappened();
    }
signals:
    void somethingHappened();
};

Qt UI:

#include <QMainWindow>
#include <QDialog>

class MyDialog : public QDialog
{
public:
    MyDialog(QWidget *parent = 0) :
        QDialog(parent)
    { show(); }

    void keyPressEvent(QKeyEvent* /*e*/){
        close();
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0) :
        QMainWindow(parent),
        mDialog(Q_NULLPTR)
    {
        qDebug()<<"Current thread:"<<QThread::currentThread();
        MyThread* myThread = new MyThread(this);
        connect(myThread, &MyThread::somethingHappened,
                this, &MainWindow::createDialog, Qt::QueuedConnection);
        myThread->start();
    }
private slots:
    void createDialog(){
        qDebug()<<"Current thread:"<<QThread::currentThread();
        if(mDialog == Q_NULLPTR)
            mDialog = new MyDialog(this);
    }
private:
    MyDialog* mDialog;
};

Some advices about your code:

void MyQObject::initialize_m_Dialog(QMainWindow* p) { m_Dialog = new MyDialog(p); }

  1. The creation of dialog should do it in UI thread, so the UI component(QMainWindow* p) cannot be the parameter.

  2. Take care the memory leak.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM