首页 Qt 学习之路 2 Qt 学习之路 2(13):对话框简介

Qt 学习之路 2(13):对话框简介

71 6K

对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。尽管 Ribbon 界面的出现在一定程度上减少了对话框的使用几率,但是,我们依然可以在最新版本的 Office 中发现不少对话框。因此,在可预见的未来,对话框会一直存在于我们的程序之中。

Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialogQDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle(tr("Main Window"));

    openAction = new QAction(QIcon(":/images/doc-open"), tr("&Open..."), this);
    openAction->setShortcuts(QKeySequence::Open);
    openAction->setStatusTip(tr("Open an existing file"));
    connect(openAction, &QAction::triggered, this, &MainWindow::open);

    QMenu *file = menuBar()->addMenu(tr("&File"));
    file->addAction(openAction);

    QToolBar *toolBar = addToolBar(tr("&File"));
    toolBar->addAction(openAction);
}

MainWindow::~MainWindow()
{
}

void MainWindow::open()
{
    QDialog dialog;
    dialog.setWindowTitle(tr("Hello, dialog!"));
    dialog.exec();
}

上面我们使用了前面的示例代码。注意看的是open()函数里面的内容。我们使用QDialog创建了一个对话框,设置其标题为“Hello, dialog!”,然后调用exec()将其显示出来。注意看的是任务栏的图标,由于我们没有设置对话框的 parent 指针,我们会看到在任务栏出现了对话框的位置:

不带 parent 的对话框

我们修改一下open()函数的内容:

void MainWindow::open()
{
    QDialog dialog(this);
    dialog.setWindowTitle(tr("Hello, dialog!"));
    dialog.exec();
}

重新运行一下,对比一下就会看到 parent 指针的有无对QDialog实例的影响。

对话框分为模态对话框和非模态对话框。所谓模态对话框,就是会阻塞同一应用程序中其它窗口的输入。模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分进行操作的。与此相反的是非模态对话框,例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。

Qt 支持模态对话框和非模态对话框。其中,Qt 有两种级别的模态对话框:应用程序级别的模态和窗口级别的模态,默认是应用程序级别的模态。应用程序级别的模态是指,当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关闭对话框,然后才能访问程序中其他的窗口。窗口级别的模态是指,该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它窗口交互。窗口级别的模态尤其适用于多窗口模式,更详细的讨论可以看以前发表过的文章

Qt 使用QDialog::exec()实现应用程序级别的模态对话框,使用QDialog::open()实现窗口级别的模态对话框,使用QDialog::show()实现非模态对话框。回顾一下我们的代码,在上面的示例中,我们调用了exec()将对话框显示出来,因此这就是一个模态对话框。当对话框出现时,我们不能与主窗口进行任何交互,直到我们关闭了该对话框。

下面我们试着将exec()修改为show(),看看非模态对话框:

void MainWindow::open()
{
    QDialog dialog(this);
    dialog.setWindowTitle(tr("Hello, dialog!"));
    dialog.show();
}

是不是事与愿违?对话框竟然一闪而过!这是因为,show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。注意,dialog 是建立在栈上的,show()函数返回,MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了。知道了原因就好改了,我们将 dialog 改成堆上建立,当然就没有这个问题了:

void MainWindow::open()
{
    QDialog *dialog = new QDialog;
    dialog->setWindowTitle(tr("Hello, dialog!"));
    dialog->show();
}

对比一下这个非模态对话框和之前的模态对话框。我们在对话框出现的时候可以与主窗口交互,因此我们可以建立多个相同的对话框:

非模态对话框

如果你足够细心,应该发现上面的代码是有问题的:dialog 存在内存泄露!dialog 使用 new 在堆上分配空间,却一直没有 delete。解决方案也很简单:将 MainWindow 的指针赋给 dialog 即可。还记得我们前面说过的 Qt 的对象系统吗?

不过,这样做有一个问题:如果我们的对话框不是在一个界面类中出现呢?由于QWidget的 parent 必须是QWidget指针,那就限制了我们不能将一个普通的 C++ 类指针传给 Qt 对话框。另外,如果对内存占用有严格限制的话,当我们将主窗口作为 parent 时,主窗口不关闭,对话框就不会被销毁,所以会一直占用内存。在这种情景下,我们可以设置 dialog 的WindowAttribute

void MainWindow::open()
{
    QDialog *dialog = new QDialog;
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->setWindowTitle(tr("Hello, dialog!"));
    dialog->show();
}

setAttribute()函数设置对话框关闭时,自动销毁对话框。另外,QObject还有一个deleteLater()函数,该函数会在当前事件循环结束时销毁该对话框(具体到这里,需要使用exec()开始一个新的事件循环)。关于事件循环,我们会在后面的文章中详细说明。

71 评论

斯啦丝拉 2013年1月9日 - 11:01

博主你好,我在Ubuntu+Qt5环境下
dialog->setAttribute(Qt::WA_DeleteOnClose); 有效
dialog->deleteLater(); 无效
还望赐教

回复
titi璇 2013年7月13日 - 16:41

我这里是使用
dialog->setAttribute(Qt::WA_DeleteOnClose); 对话框不会一闪而过;
而使用dialog->deleteLater(); 则对话框一闪而过。请问为什么啊?
而且怎么看到底有没有内存泄露?

回复
豆子 2013年7月17日 - 15:42

这个是代码的一个错误,如果是 show() 函数的话,只能使用 dialog->setAttribute(Qt::WA_DeleteOnClose);如果要使用 dialog->deleteLater(),则必须使用 exec() 函数。这是因为 deleteLater() 会在事件循环结束时执行,show() 不开始新的事件循环,所以会一闪而过。

至于内存泄露怎么看,除了使用一些工具之外,只能检查代码是不是每个 new 对应一个 delete 这种办法。内存泄露是很难发现的,即使是工具也不一定全部检查出来,所以更多地要依靠经验。

回复
特地来支持豆子 2013年6月7日 - 01:46

谢谢楼主 让我学到了知识

回复
恒古炎 2013年9月8日 - 00:44

请问下对话框有办法自己去设置窗口的最大化和那个问号按钮吗?那个按钮有时候没什么用啊 有办法取消那个按钮?

回复
豆子 2013年9月9日 - 09:10

通过设置 windowFlags() 达到目的。可以查看 Qt::WindowFlags 的文档,比如只需要最小化和关闭按钮,就设置为 Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint

回复
恒古炎 2013年9月9日 - 10:43

哦 原来在其它类里面 我只看了对话框类 囧 多谢豆子哥

回复
otto 2013年10月11日 - 15:31

豆子哥,请教下
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();还有
QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
的区别,其中的.和->的用法 😥

回复
豆子 2013年10月11日 - 20:31

. 运算符用于实例变量;->运算符用于指针变量。这是 C++ 基础,详细信息请自行查阅

回复
2013年12月9日 - 15:32

你好,我觉得最后一段代码还是值得商榷:

void MainWindow::open()
{
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
}

这样对话框虽然在关闭时可以自动销毁,但是由于没有指定parent,所以MainWindow关闭时,对话框不会关闭,这个感觉很不好~但是如果

QDialog *dialog = new QDialog(this);

这样指定parent,又无法避免您在之前提及“对话框有可能不是在一个界面类中出现”的局限性,不知可有好的解决办法?

回复
豆子 2013年12月9日 - 15:39

个人觉得这种代码只是演示性质的。“对话框有可能不是在一个界面类中出现”一般不会出现。如果出现,说明你的分层不够清晰:一般非界面类作为业务逻辑类,而对话框是界面类,二者混在一起显然不是良好的设计。正常的设计应该是界面与逻辑完全分离。所以如果有这种问题,那么在想出这种技巧之前,应该把架构重新设计一下了。比如将运算结果通过参数传递回界面类等。

回复
lauthor 2013年12月14日 - 18:13

非常有用

回复
折一 2014年1月11日 - 13:40

你好,请问如何实现win式正规的子窗口?
也就是说,当弹出子窗口时,父窗口不允许接受点击键盘输入等事件,而子窗口则获得焦点,而且这个子窗口只能在父窗口的区域内移动,即是说要内嵌在父窗口里。

回复
豆子 2014年1月13日 - 11:15

没有听说过“正规子窗口”这个名字。子窗口阻塞父窗口输入是模态的,只能在父窗口内部移动可以使用 QMdiArea 实现。像你说的窗口类型,可能要重写窗口移动的事件,判断是不是在父窗口内部了。

回复
大灰狼嘎嘎 2014年1月26日 - 13:45

所谓的模态Dialog就是将当前线程放入阻塞队列,
所谓的非模态Dialog就是再创建一个线程专门用来显示对话框,
对吧?

回复
豆子 2014年1月26日 - 21:15

可以这么理解,事实上,模态、非模态是针对事件循环的。不过事件循环其实也是在一个新的线程里面的

回复
Huang Zhen 2019年11月10日 - 13:42

我发现另外还有一种情况,如果在栈上创建对话框,然后按顺序调用show和exec,这样生成的对话框是非模态而且阻塞的。

回复
Const_Lin 2014年3月6日 - 14:27

豆子你好。
“将 MainWindow 的指针赋给 dialog ”
意思是不是将dialog加到MainWindow的children()列表?
还是相反?

回复
Const_Lin 2014年3月6日 - 14:29

不好意思。没看仔细。已经搞懂。

回复
豆子 2014年3月10日 - 10:14

是的,意思就是把 dialog 作为 MainWindow 的一个子对象,也就是添加到其 children 列表。这样,当 parent 析构的时候,dialog 就会被析构。

回复
Pegasus 2014年8月18日 - 11:49

请问怎么添加到children列表

回复
豆子 2014年8月20日 - 21:10

当设置了parent属性时,就把这个对象添加到了parent属性的children列表。

回复
大风 2015年5月4日 - 18:39

我估计是被传入父对象指针的对象会根据这个指针向父对象传递包含自身指针的某种信息,然后父对象将传过来的指针包含在children列表中。

gzy0011 2014年6月13日 - 00:21

void MainWindow::open()
{
QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
}

void MainWindow::open()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();
}

这两段代码一个建立在堆上 一个建立在站上 不就是一个用的实变量 一个是指针
为什么说是堆上或是站上?

回复
豆子 2014年6月13日 - 09:00

这是基础 C++ 的内容,使用 new 运算符是在堆上创建,不使用 new 运算符则是在栈上创建。这与变量和指针没有关系。

回复
xuan120520 2014年6月28日 - 12:42

豆子老师,我在您的《Qt之路学习1》中的第8节--《创建一个对话框(下)》中,看到有一句代码不太明白,设置固定的高度时,用的是setFixedHeight(sizeHint().height());这个函数中参数是两个函数,且中间加个点,这是什么意思了?之前好像都没遇到过呀,谢谢豆子!

回复
豆子 2014年6月28日 - 18:10

sizeHint().height() 就是直接调用了 sizeHint() 函数返回值的 height() 函数。只不过省去了使用中间变量的步骤。

回复
xuan120520 2014年6月28日 - 20:43

谢谢豆子,明白了。我得补习下C++。

回复
cg 2014年9月1日 - 09:39

豆子老师,
QDialog dialog; 没问题
QDialog dialog(this); 会有以下问题
Starting D:\LearnQT\build-Dialog-Desktop_Qt_5_3_MinGW_32bit-Debug\debug\Dialog.exe...
setGeometry: Unable to set geometry 100x30+690+311 on QWidgetWindow/'QDialogClassWindow'. Resulting geometry: 160x30+690+311 (frame: 9, 38, 9, 9, custom margin: 0, 0, 0, 0, minimum size: 0x0, maximum size: 16777215x16777215).
搞不懂,请指教,谢谢。

回复
豆子 2014年9月2日 - 09:45

不清楚你写的哪一段程序?这个错误有可能是因为没有调用 setGeometry() 函数造成的,原因可能有很多,比如资源没有加载完成之类。

回复
瞬间 2016年8月31日 - 00:06

解决方法:
dlog = new QDialog(this);
dlog->setGeometry(QRect(200, 200, 200 ,200));

回复
叔叔级人物 2014年9月1日 - 17:36

楼主,关于最后那里将exec()修改为show()后,对话框真的一闪而过了。
楼主你的方法是将其创建在堆上解决这个问题。
但我尝试在dialog show();下面加上dialog exec();也可以实现对话框没有一闪而过,而且对话框不关闭的情况下,我还可以点击mainwindow。
我这种方法有什么问题?

回复
豆子 2014年9月2日 - 09:54

没有见到这种实现,如果也能构成非模态对话框,应该也是可行的吧 ;-P

回复
怀想 2015年8月9日 - 11:10

你这种方法会造成内存泄漏,对话框无法被析构

回复
qt 初学者 2015年6月14日 - 15:34

老师 我想实现 按一个button 就弹出 dialog
void MainWindow::on_toolButton_nl_clicked()
{
Dialognl *dialog = new Dialognl;
dialog->show();
// dialognl.show();
}
但我现在这个dialog 不能保存其dialog里的内容,因为每次clicked 就会new ,我想能不能把 Dialognl *dialog = new Dialognl;这个放在void MainWindow::on_toolButton_nl_clicked()外面 结果 运行不报错,但总是弹出
this application has requested the runtime to term 这个对话框.....怎么办?

回复
豆子 2015年6月15日 - 09:27

放在函数外面作为成员变量应该是没有问题的,可能是什么地方写错了。另外,如果需要保存的话,应该在对话框中完成,将需要保持的数据保存下来(保存到磁盘或者数据库),而不是交给外层的主窗口完成。

回复
davidzwb 2015年6月27日 - 16:23

请问为什么建立pushbutton等widget时就不需要exec(), show()等, QDialog就需要呢,是因为它比较特殊吗?

回复
豆子 2015年6月29日 - 10:37

对话框是一个独立的窗口,需要有自己的事件循环,接收用户响应,而按钮之类只是组件,是由窗口的事件循环支持的。

回复
davidzwb 2015年6月29日 - 21:13

明白了,多谢!

回复
xululu 2015年10月14日 - 16:47

打错字了:
最近看QT教程看到这篇我的有错误,应该是包含文件的问题吧
dialog->setAttribute(QT::WA_DeleteOnClose);
出现错误(1)是QT:不是类或命名空间名称,
(2)WA_DeleteOnClose :未声明的标识符。求解答

回复
豆子 2015年10月15日 - 15:43

应该是 Qt::WA_DeleteOnClose,注意大小写

动感超人 2015年11月7日 - 18:20

"对于一下这个非模态对话框和之前的模态对话框。"这里应该是“对比”吧?

回复
豆子 2015年11月9日 - 10:11

已经修改过了,感谢指出!

回复
2015年12月8日 - 01:08

受教了,但是我要吐槽为什么下一篇在左,上一篇在右

回复
豆子 2015年12月8日 - 16:11

好吧,发现这个问题了 ;-P 现在修改了下,应该可以了的 ^^

回复
动感超人 2016年3月7日 - 16:54

请问如果我不指定 QDialog *dialog = new QDialog 这个指针的父亲,那他的父亲默认是谁?

回复
豆子 2016年3月8日 - 10:13

不指定的话就没有父组件,此时这个对话框会在任务栏多出一个自己的窗口。当你使用 new 创建时,就需要自己手动 delete 才可以。除此以外,没有任何区别。

回复
动感超人 2016年3月8日 - 13:02

tks

回复
王月 2016年4月1日 - 22:41

豆子老师,请问一下,除了dialog->setAttribute(Qt::WA_DeleteOnClose);这样在窗口设置属性能删除以外,我能通过绑定事件去删除此对话框吗?因为new了不delete,有强迫症感觉真的不太爽,或者是否是connect(dialog,signal(quit()),this,deleteDialog()),在deleteDialog()里实现删除dialog,这样可以吗?

回复
le 2016年4月26日 - 13:00

豆子,第一个代码的QopenAction定义的时候是不是少写了QAction*呢

回复
豆子 2016年4月26日 - 15:02

这个 QAction * 是在头文件中的,所以没有写出来。你也可以直接在构造函数中添加 QAction *

回复
ccppaa 2016年8月22日 - 15:15

大神,参照qt里的例子使用QSystemTrayIcon类,点击按钮将Dialog隐藏,并在系统系统托盘中显示程序logo,
//将icon设到QSystemTrayIcon对象中
mSysTrayIcon->setIcon(QIcon(":/recources/recourses/logo.ico"));

QIcon icon = QIcon(":/recources/recourses/logo.ico");
但是这一句没有实现,功能已经成功实现了,两种方法都不行,资源路径没有问题,在其他地方可以正常使用,麻烦大神看一下

回复
小怪 2016年12月19日 - 08:57

老师 您好 我做了一个简单的文件复制应用程序 添加了一个进度对话框来提示进度 程序之前用Qt5.4.2编译一切正常 在更新到5.7.1后 每次启动程序后 进度对话框在隔几秒后自动弹出。很不理解

回复
豆子 2016年12月25日 - 09:13

这个我也不大清楚,需要看到代码才能了解具体问题

回复
2018年1月10日 - 23:20

豆子老师,想问一下顶层窗口与非顶层窗口的区别,看了上面的解释,还是不太理解,文章第二段末尾的“顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。”,什么叫做“顶层窗口在任务栏会有自己的位置”,哪里是任务栏?运行代码比较了下,不带this指针时,点击工具栏图标后,会出现一个比原窗口大的新窗口,这个新窗口遮盖住了原窗口,,而带上this指针后,运行代码,点击工具栏图标后,新的窗口出现在了原窗口的中心位置,新窗口比原窗口小,除了这个区别,没觉得有哪些其他的不同,希望您解惑一下,顶层窗口与非顶层窗口的区别。谢谢了

回复
豆子 2018年1月13日 - 21:21

任务栏是系统屏幕下面的系统任务栏,如果是顶层窗口的话,任务栏会有一个窗口自己的图标,不是顶层窗口就不会有这个图标。一般会认为,一个应用程序在运行时,任务栏应该只有一个图标,也就是说,顶层窗口应该只有一个。

回复
2018年1月15日 - 16:34

好的,明白了,谢谢您的回复

回复
2018年1月15日 - 22:35

豆子老师,有个问题想问您,就是在Qt中如何查看一个类中的函数的函数体,我使用的Qt5.6版本(当然这可能跟使用的版本无关),比如QWidget类中有个show()函数,我想看一下这个函数的函数体,看看它具体的代码是怎么写的,这个可以查看吗?在Qt的帮助文档中,我只找到了函数的应用说明,就是对它的介绍,并没有找到源码,函数的源码是看不到的吗?还是我的查找方法不对?

回复
小郭 2019年5月29日 - 11:54

老师,您好!我现在有个问题是,我写的一个弹出来的模态对话框不能单独移动,只要移动,父窗口也会跟随移动是怎么回事呢?而且父窗口会变暗,弹出来的对话框不变暗。

回复
hhdx 2019年9月23日 - 10:03

谢谢豆子老师,以前的好多疑问都解决了。

回复
franzhong 2019年11月1日 - 12:15

QDialog *dialog = new QDialog();
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
//程序关了,对话框没有关闭。但是
QDialog *dialog = new QDialog(this);才行
说明Qt::WA_DeleteOnClose没效吗?

回复
franzhong 2019年11月1日 - 12:18

哈哈,明白了,这是两个问题,一个是继承(树)关系,另一个是防泄露用途

回复
Aincrad 2020年2月16日 - 11:15

豆子老师你好,我想问一下,为什么Qt不把“窗口关闭”和"内存释放"绑定在一起呢,检测到窗口关闭了就自动调用相应对象析构,这样不是就不需要担心内存泄漏了吗

回复
豆子 2020年2月28日 - 10:22

对于 Qt 应用内部的窗口,比如弹出的对话框,是可以的,就是设置 parent 指针。但是对于主窗口,窗口关闭后,Qt 已经没有机会去释放资源,只能留给操作系统去回收。但这也只是我的一点猜测,具体有没有另外的原因还不知道。

回复
colmk 2020年3月29日 - 23:54

又明白了一些关于堆跟栈的知识,谢谢豆子

回复
ziggystardust 2020年6月11日 - 18:09

豆子老师,想问一下,为什么加入了这行代码 ui->setupUi(this);运行后的菜单栏就没有显示了呢

回复
豆子 2020年7月27日 - 22:13

这段代码是加载 designer 的设计的,如果 designer 中没有菜单就不会显示了

回复
tron 2020年10月8日 - 22:25

//这样当主被关闭的时候这些也会被关闭,怎么写呢?
QDialog *dialog = new QDialog(this);
而不是
dialog->setParent(this);
为什么第二个不行,具体表现为”根本不弹出QDialog “。

回复
zhangS 2020年10月25日 - 14:33

请问对话框用类来实现,例如:
WindowUi(这个是和主窗口类似的一个类,继承的QMainWindow),然后执行到了,WindowUi.shou这一步来了,为什么不显示呢?

回复
范效萌 2020年11月5日 - 12:56

文章中有这样一段话:【如果你足够细心,应该发现上面的代码是有问题的:dialog 存在内存泄露!dialog 使用 new 在堆上分配空间,却一直没有 delete】。
我想问的是:在这种情况下,即使我们关闭了MainWindow,那么我们创建的这个小dialog,在堆中的内存还是得不到释放,还是存在内存泄漏吗?

回复
豆子 2020年11月17日 - 14:43

这种情况下操作系统会回收整个进程的内存,一般不会存在泄漏。内存泄露一般发生在进程一直存在的情况。

回复

回复 franzhong 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2