在主线程中创建 QDialog

Create a QDialog in main thread

本文关键字:创建 QDialog 线程      更新时间:2023-10-16

我是Qt的新手,在创建新对话框时遇到了问题。这是它的类:

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);
}

在另一个线程而不是主线程中,我正在这样做:

m_Dialog = new MyDialog(parent);

但这是说我无法在另一个线程中创建新的小部件。所以我尝试了:

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));

但我不太确定我在做什么... :) 我对此陷入了僵局。

我怎样才能做到这一点?

现有代码的设计很烂。让我们修复它,从对话框开始。

Ui::类不应是公共基础。它们是实现细节,永远不应在对话框之外的任何位置公开。对话框是一个抽象:您可以对其执行一些操作。它们应该是方法,并在内部访问Ui::对象。对话框也不应依赖于父窗口的任何特定类型。如果对话框应该与其他一些对象交互,它应该发出信号,然后连接到主窗口。

为了演示它,假设对话框具有QLineEdit edit元素。可以设置文本,并通知其他人文本中的更改。它应设计如下:

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;
};  

现在,让我们看看如何从任何线程访问它。关键是发送一些代码在主线程中执行。有关详细信息,请参阅此答案。该段代码(函子)应携带设置对话框所需的所有数据。

然后:

// 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
});
}

setupDialog函数是线程安全的,只要线程的寿命不超过parent,就可以在任何线程中执行。

请注意,上面的代码本质上是非阻塞的。函子包装在一个事件中,并传递到主线程的事件调度器,然后执行函子。执行setupDialog的线程只能在主线程的事件队列的互斥锁上获得内容。这种互斥锁只是偶尔持有,持续时间很短。

我在下面的不同线程中创建了一个用于创建 UI 组件的演示,MyThread启动后会发出信号。如果 UI 线程收到信号,将创建并显示dialog

我的线程:

#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;
};

关于代码的一些建议:

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

  1. dialog的创建应该在 UI 线程中完成,因此 UI 组件 (QMainWindow* p) 不能作为参数。

  2. 注意内存泄漏。