0

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?

João Paulo
  • 6,300
  • 4
  • 51
  • 80
  • And the message is correct, you should not create an element of the GUI in another thread. Read the following: https://doc.qt.io/qt-5.10/thread-basics.html#gui-thread-and-worker-thread – eyllanesc Mar 22 '18 at 20:52
  • Why do you want to use another thread? – eyllanesc Mar 22 '18 at 20:53
  • I don't really want... I received a large program to fix and it's happening in this way. – João Paulo Mar 22 '18 at 20:54
  • Well, you have a bad design, what does *large program* mean? – eyllanesc Mar 22 '18 at 20:55
  • That I can't easily know why it's being created in another thread. – João Paulo Mar 22 '18 at 20:56
  • I also, searched for `moveToThread`... but It only changes the thread affinity... – João Paulo Mar 22 '18 at 20:57
  • Oh well, neither do I. Do you think we will give you a magic solution? I recommend you talk to the people who developed the program and read the documentation of your project. – eyllanesc Mar 22 '18 at 20:58
  • Exactly, that is your goal, that will not be your solution, with the information you provide it will be impossible to help you, and I think you understand it. – eyllanesc Mar 22 '18 at 20:59
  • I didn't wait for a magic solution. I just thought that would be possible the send a signal to main thread, lock the event loop to execute it or something similar. Maybe Qt already has something to help in these cases. – João Paulo Mar 22 '18 at 21:00
  • No, Qt expects, requires and demands that the entire GUI be executed on the main thread, if there are heavy tasks to be performed on another thread, and when the result is obtained it is sent to the main thread through signals. – eyllanesc Mar 22 '18 at 21:01
  • Got it! I'll try something else then. But I was trying something like [this](https://stackoverflow.com/questions/5128541/ways-to-create-a-qdialog-outside-the-main-thread). – João Paulo Mar 22 '18 at 21:03
  • What you do is call the show function from another thread, but the QDialog lives in the main thread, it is similar to a signal. – eyllanesc Mar 22 '18 at 21:04
  • 1
    You can declare the `MyDialog` in the main thread's class (parent class itself) and trigger its instantiation and displaying using a signal from the child class that you are trying to create it from. – Aditya Mar 22 '18 at 21:38

2 Answers2

3

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 e.g. 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.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
2

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.

JustWe
  • 4,250
  • 3
  • 39
  • 90