前言
在 Qt 框架中,QDialog 用于创建对话框。它提供了三种主要的显示方法:open()
、exec()
和 show()
。这些方法虽然都能显示对话框,但在行为和使用场景上有很大区别。本文将通过分析 QDialog 源码,深入理解这三个方法的区别及其适用场景。
QDialog 的显示方法简述
首先,让我们简要了解这三种方法:
show()
:非模态显示对话框open()
:窗口模态显示对话框,立即返回exec()
:模态显示对话框,阻塞直到用户关闭
show () 方法解析
show()
是 QWidget 的方法,QDialog 继承了此方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| void QWidget::show()
{
Qt::WindowState defaultState = QGuiApplicationPrivate::platformIntegration()->defaultWindowState(data->window_flags);
if (defaultState == Qt::WindowFullScreen)
showFullScreen();
else if (defaultState == Qt::WindowMaximized)
showMaximized();
else
setVisible(true); // Don't call showNormal() as not to clobber Qt::Window(Max/Min)imized
}
void QWidget::setVisible(bool visible)
{
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) == !visible)
return;
// Remember that setVisible was called explicitly
setAttribute(Qt::WA_WState_ExplicitShowHide);
Q_D(QWidget);
d->setVisible(visible);
}
|
特点:
- 非模态显示(默认情况下)
- 立即返回,不阻塞调用线程
- 用户可以与应用程序的其他窗口交互
- 不会自动创建事件循环
示例:
1
2
3
| QDialog *dialog = new QDialog(this);
// 设置对话框内容...
dialog->show();
|
open () 方法解析
open()
是 Qt 4.5 引入的方法,用于显示窗口模态对话框:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| void QDialog::open()
{
Q_D(QDialog);
// 获取对话框当前的窗口模态类型
Qt::WindowModality modality = windowModality();
// 然后检查是否已经是窗口模态(Qt::WindowModal)。如果不是窗口模态,则修改当前的模态设置
if (modality != Qt::WindowModal) {
// 保存原来的模态类型
d->resetModalityTo = modality;
// 记录WA_SetWindowModality属性是否被设置
d->wasModalitySet = testAttribute(Qt::WA_SetWindowModality);
setWindowModality(Qt::WindowModal);
// 清除WA_SetWindowModality属性,表示这个模态状态不是由用户显式设置的
setAttribute(Qt::WA_SetWindowModality, false);
#ifdef Q_OS_MAC
// 在 macOS 上,对话框被设置为 Sheet 样式,这是 macOS 特有的窗口模态对话框视觉样式。
setParent(parentWidget(), Qt::Sheet);
#endif
}
// 将对话框结果重置为0
setResult(0);
show();
}
|
特点:
- 窗口模态显示(阻止与父窗口的交互)
- 立即返回,不阻塞调用线程
- 保存原有模态状态,然后设置为窗口模态
- 异步操作,适合基于信号-槽的事件处理
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| QDialog *dialog = new QDialog(this);
// 设置对话框内容...
// 关联信号槽
connect(dialog, &QDialog::finished, this, &MyWidget::handleDialogResult);
dialog->open();
MyWidget::handleDialogResult(int result)
{
if (result == QDialog::Accepted)
// ...
else if (result == QDialog::Rejected)
// ...
}
|
exec () 方法解析
exec()
是最传统的对话框显示方法,用于显示模态对话框:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| int QDialog::exec()
{
Q_D(QDialog);
// 检测是否发生了递归调用 exec()。如果对话框已经有一个事件循环在运行(即 d->eventLoop 不为空),则发出警告并返回 -1。
if (Q_UNLIKELY(d->eventLoop)) {
qWarning("QDialog::exec: Recursive call detected");
return -1;
}
// 保存并暂时禁用 Qt::WA_DeleteOnClose 属性,确保对话框在关闭时不会被自动删除
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_DeleteOnClose, false);
// 重置由 open() 方法设置的模态性设置
d->resetModalitySetByOpen();
// 记录并设置 Qt::WA_ShowModal 属性,表明这是一个模态对话框
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
setAttribute(Qt::WA_ShowModal, true);
// 将对话框结果重置为 0
setResult(0);
// 显示对话框并创建事件循环
show();
QPointer<QDialog> guard = this;
// 检查是否使用本地对话框实现:
// - 如果使用本地对话框,调用平台辅助对象的 exec()
// - 否则,创建并执行一个本地事件循环,这会阻塞当前线程,直到调用 QEventLoop::exit() 或 QEventLoop::quit()
if (d->nativeDialogInUse) {
d->platformHelper()->exec();
} else {
QEventLoop eventLoop;
d->eventLoop = &eventLoop;
// 使用标志 QEventLoop::DialogExec,这是专门为对话框执行设计的模式。
(void) eventLoop.exec(QEventLoop::DialogExec);
}
if (guard.isNull())
return QDialog::Rejected;
d->eventLoop = nullptr;
// 恢复状态
setAttribute(Qt::WA_ShowModal, wasShowModal);
int res = result();
if (d->nativeDialogInUse)
d->helperDone(static_cast<QDialog::DialogCode>(res), d->platformHelper());
if (deleteOnClose)
delete this;
return res;
}
|
特点:
- 模态显示(默认为应用程序模态)
- 阻塞调用线程,直到对话框关闭
- 创建并执行本地事件循环
- 返回对话框的结果代码(
QDialog::Accepted
或 QDialog::Rejected
) - 支持防止递归调用的检测
- 可以防止对话框在显示期间被删除
示例:
1
2
3
4
5
6
7
| QDialog dialog(this);
// 设置对话框内容...
if (dialog.exec() == QDialog::Accepted) {
// 处理接受的情况
} else {
// 处理拒绝的情况
}
|
最佳实践建议
在源码中,Qt 官方明确推荐使用 open()
而非 exec()
:
\note Avoid using this function; instead, use \c{open()}. Unlike exec(),
open() is asynchronous, and does not spin an additional event loop. This
prevents a series of dangerous bugs from happening (e.g. deleting the
dialog’s parent while the dialog is open via exec()). When using open() you
can connect to the finished() signal of QDialog to be notified when the
dialog is closed.
虽然官方推荐使用 open()
,但是需要注意的是,在 macOS 平台上,会启用 Sheet 样式,会有系统默认遮罩。如果项目自身实现了遮罩,会和系统默认遮罩叠加,还会有窗口闪烁的情况。
而 exec()
存在无法从 DialogA 里再创建一个 DialogB 时,先隐藏 DialogA,当 DialogB 完成时,再将 DialogA 显示出来的问题。
结论
通过对 QDialog 源码的分析,我们深入理解了 open()
、exec()
和 show()
三种方法的工作原理和区别,从而在实际工程使用时,能够合理选择不同的方法。