Reputation: 6193
In my application I have a QDialog
which itself contains a complex, QWidget
-derived GUI element. The QDialog
is modal and opened with exec()
and the embedded GUI element handles all user interactions.
So only this child QWidget
knows when the QDialog
can be closed, which is done this way:
QDialog* parent=qobject_cast<QDialog*>(parentWidget());
if (parent) parent->close();
This is necessary because the QDialog
has to be closed and not only the QWidget
.
Now a user reported a situation where QDialog::exec()
has returned but where the dialog (or only the GUI element?) was still visible. From the log files I can see QDialog::exec()
really has returned and the code right after this call was executed.
So my current assumption: the GUI element has lost its parent so that the close()
call shown above was not called because "parent" was null.
Any idea how this can happen? Is there a regular way where the parent of a QWidget
can disappear?
Upvotes: 0
Views: 421
Reputation: 98425
Generally speaking, using QDialog::exec
to reenter the event loop will cause trouble, because suddenly all the code that runs in the main thread must be reentrant. Most likely you're facing fallout from that. Don't reenter the event loop, and you'll be fine or the problem will become reproducible.
If you need to react to the dialog being accepted or rejected, connect code to the relevant slots. I.e. change this:
void do() {
MyDialog dialog{this};
auto rc = dialog.exec();
qDebug() << "dialog returned" << rc;
}
to that:
class Foo : public QWidget {
MyDialog dialog{this};
...
Foo() {
connect(&dialog, &QDialog::done, this, &Foo::dialogDone);
}
void do() {
dialog.show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
or, if you want to lazily initialize the dialog:
class Foo : public QWidget {
MyDialog * m_dialog = nullptr;
MyDialog * dialog() {
if (! m_dialog) {
m_dialog = new MyDialog{this};
connect(m_dialog, &QDialog::done, this, &Foo::dialogDone);
}
return m_dialog;
}
...
void do() {
dialog()->show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
It is a horrible antipattern for the child widget to attempt to meddle with the parent. The knowledge that the widget has a parent should not leak into the widget, it should be localized to the parent. Thus, the child widget should emit a signal that indicates that e.g. the data was accepted. When you create the dialog, connect this signal to the dialog's accept()
or close()
slots:
class MyWidget : public QWidget {
Q_OBJECT
public:
Q_SIGNAL void isDone();
...
};
class MyDialog : public QDialog {
QGridLayout layout{this};
MyWidget widget;
public:
MyDialog() {
layout.addWidget(&widget, 0, 0);
connect(&widget, &MyWidget::isDone, this, &QDialog::accepted);
}
};
Upvotes: 1