首页 Qt 学习之路 2 Qt 学习之路 2(14):对话框数据传递

Qt 学习之路 2(14):对话框数据传递

61 6

对话框的出现用于完成一个简单的或者是短期的任务。对话框与主窗口之间的数据交互相当重要。本节将讲解如何在对话框和主窗口之间进行数据交互。按照前文的讲解,对话框分为模态和非模态两种。我们也将以这两种为例,分别进行阐述。

模态对话框使用了exec()函数将其显示出来。exec()函数的真正含义是开启一个新的事件循环(我们会在后面的章节中详细介绍有关事件的概念)。所谓事件循环,可以理解成一个无限循环。Qt 在开启了事件循环之后,系统发出的各种事件才能够被程序监听到。这个事件循环相当于一种轮询的作用。既然是无限循环,当然在开启了事件循环的地方,代码就会被阻塞,后面的语句也就不会被执行到。因此,对于使用了exec()显示的模态对话框,我们可以在exec()函数之后直接从对话框的对象获取到数据值。

看一下下面的代码:

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

上面的代码中,我们使用exec()显示一个模态对话框。最后一行代码,qDebug()类似于std::cout或者 Java 的System.out.println();语句,将后面的信息输出到标准输出,一般就是控制台。使用qDebug()需要引入头文件。在exec()函数之后,我们直接可以获取到 dialog 的数据值。注意,exec()开始了一个事件循环,代码被阻塞到这里。由于exec()函数没有返回,因此下面的result()函数也就不会被执行。直到对话框关闭,exec()函数返回,此时,我们就可以取得对话框的数据。

需要注意的一点是,如果我们设置 dialog 的属性为WA_DeleteOnClose,那么当对话框关闭时,对象被销毁,我们就不能使用这种办法获取数据了。在这种情况下,我们可以考虑使用 parent 指针的方式构建对话框,避免设置WA_DeleteOnClose属性;或者是利用另外的方式。

实际上,QDialog::exec()是有返回值的,其返回值是QDialog::Accepted或者QDialog::Rejected。一般我们会使用类似下面的代码:

QDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
    // do something
} else {
    // do something else
}

来判断对话框的返回值,也就是用户是点击了“确定”还是“取消”。更多细节请参考QDialog文档。

模态对话框相对简单,如果是非模态对话框,QDialog::show()函数会立即返回,如果我们也这么写,就不可能取得用户输入的数据。因为show()函数不会阻塞主线程,show()立即返回,用户还没有来得及输入,就要执行后面的代码,当然是不会有正确结果的。那么我们就应该换一种思路获取数据,那就是使用信号槽机制。

由于非模态对话框在关闭时可以调用QDialog::accept()或者QDialog::reject()或者更通用的QDialog::done()函数,所以我们可以在这里发出信号。另外,如果找不到合适的信号发出点,我们可以重写QDialog::closeEvent()函数,在这里发出信号。在需要接收数据的窗口(这里是主窗口)连接到这个信号即可。类似的代码片段如下所示:

//!!! Qt 5
// in dialog:
void UserAgeDialog::accept()
{
    emit userAgeChanged(newAge); // newAge is an int
    QDialog::accept();
}

// in main window:
void MainWindow::showUserAgeDialog()
{
    UserAgeDialog *dialog = new UserAgeDialog(this);
    connect(dialog, &UserAgeDialog::userAgeChanged, this, &MainWindow::setUserAge);
    dialog->show();
}

// ...

void MainWindow::setUserAge(int age)
{
    userAge = age;
}

上面的代码很简单,这里不再赘述。另外,上述代码的 Qt 4 版本也应该可以很容易地实现。

不要担心如果对话框关闭,是不是还能获取到数据。因为 Qt 信号槽的机制保证,在槽函数在调用的时候,我们始终可以使用sender()函数获取到 signal 的发出者。关于sender()函数,可以在文档中找到更多的介绍。顺便说一句,sender()函数的存在使我们可以利用这个函数,来实现一个只能打开一个的非模态对话框(方法就是在对话框打开时在一个对话框映射表中记录下标记,在对话框关闭时利用sender()函数判断是不是该对话框,然后从映射表中将其删除)。

61 评论

eagle 2012年10月30日 - 16:23

Qt有无根据.h文件生成cpp文件的指令?

回复
DevBean 2012年10月30日 - 22:24

Qt 本身没有类似的工具,IDE 可能会提供。例如在 Qt Creator 中,你可以在头文件的函数中点右键,会有“在 CPP 文件中生成实现”类似的功能。

回复
xuefu 2013年3月12日 - 15:28

”sender() 函数的存在使我们可以利用这个函数,来实现一个只能打开一个的非模态对话框。“

实在不理解这句话什么意思。。。”一个只能打开一个的非模态对话框“???

回复
豆子 2013年3月13日 - 09:18

由于非模态对话框不会阻塞用户的后续操作,因此你可以打开很多个非模态对话框。有时我们需要只能打开一个的非模态对话框(比如查找对话框),此时,我们可以利用 sender() 函数进行判断。

回复
xuefu 2013年3月13日 - 12:39

也就是说可以利用sender()函数来控制主窗口无法打开多个相同的非模态对话框。。。但仍然可以在主窗口上打开多个不同的非模态对话框。。。不知道这样理解是否正确?

回复
豆子 2013年3月13日 - 15:19

是的,这也只是一种解决思路

回复
tomisacat 2013年8月12日 - 13:22

最后一段代码是要新建一个继承自Dialog的UserAgeDialog类,然后在类里添加那些代码吗?

回复
豆子 2013年8月12日 - 15:03

是的,这只是一些代码片段,描述了一下大致的思路

回复
xiaoyu 2014年4月2日 - 11:29

可以把所有代码写出来么?对像我这样初学者还是很难哦。

回复
林海 2014年5月8日 - 11:07

请教一下老师,为什么这句UserAgeDialog *dialog = new UserAgeDialog(this);会出问题?
编译后它如下提示:
C:\Qt\Qt5.2.0\Tools\QtCreator\bin\untitled6\mainwindow.cpp:42: error: C2664: “UserAgeDialog::UserAgeDialog(const UserAgeDialog &)”: 不能将参数 1 从“MainWindow *const ”转换为“const UserAgeDialog &”
原因如下: 无法从“MainWindow *const ”转换为“const UserAgeDialog”
无构造函数可以接受源类型,或构造函数重载决策不明确

回复
豆子 2014年5月8日 - 14:50

UserAgeDialog 应该有一个接受 QWidget * 的构造函数,应该是你少了这个构造函数。

回复
lucia 2014年5月19日 - 21:33

怎么才能不用QT自带的API将其他程序里的数据输出到界面上呢,类似于摸你的文本编辑器

回复
豆子 2014年5月23日 - 14:24

这个不是很清楚什么意思?

回复
rainc 2014年6月12日 - 22:36

豆子,难道这里UserAgeDialog *dialog = new UserAgeDialog(this);
connect(dialog, &UserAgeDialog::userAgeChanged, this, &MainWindow::setUserAge);
dialog->show();
不应该用setAttribute()函数销毁对话框吗?

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

可以的,不过因为有 this 指针,当主窗口关闭时,UserAgeDialog 也会被销毁,因此也没有内存泄露。

回复
rainc 2014年6月14日 - 11:34

如果mainwindow这个主窗口不关闭的情况下,我打开一个对话框,然后又关闭,然后又重新打开,关闭,如此反复,那么,这样做不是会造成程序奔溃?因为主窗口没有关闭,对话框一直占用内存。豆子哥,这样理解对吗?如果按照我这个理解的话,我觉得setAttribute()函数必须要有。

回复
豆子 2014年6月18日 - 11:19

是的,理论上存在这个问题。不过感觉在现在的机器上面仅靠这种操作想把内存耗尽,应该还是蛮困难的。不过,尽管如此,设置 attribute 也是一个好的习惯,所以还是应该设置才对。

回复
cui 2014年7月4日 - 11:06

豆子老师,请问在哪里可以看到Qt的官方文档,英文的也可以,比如你上面提到的QDialog的文档。

回复
豆子 2014年7月4日 - 14:29

当你安装好 Qt 之后,文档也一并安装。你可以在 Qt Creator 左侧的帮助里面看到。或者在 bin 目录下直接打开 assistant 程序。

回复
yjcoshc 2014年7月16日 - 10:37

您好!我有想问题,就是如何将对话框的控件和MainWindow的控件通过信号槽连接起来。我想把slider放到一个对话框里,把spinbox放到MainWindow里,然后打开对话框后拖动slider,MainWindow里的spinbox数据发生相应改变,该如何做?

回复
豆子 2014年7月16日 - 10:46

你可以把 slider 的信号通过对话框 emit 出来,或者将 slider 暴露出来。不过按照面向对象的封装要求,不应该把内部组件暴露出来,所以最好的方法是把 slider 的信号通过对话框发出。这样就可以把主窗口与对话框相连。

回复
good 2014年9月2日 - 19:34

emit的方式如何实现

回复
豆子 2014年9月3日 - 09:07

直接发出信号就好了:connect(*oneWidget, SIGNAL(s1), this, SIGNAL(s2));

回复
good 2014年9月3日 - 09:45

因为slider是在dialog里的,spinbox是在mainwindow里的,不知道应该把connect放在哪里,放在mainwindow里sender不知道怎么指定,放在带slider的dialog里receiver又不知道怎么指定 :-p

madao 2014年7月16日 - 11:31

豆子老师,为什么我每次使用dialog( this)的时候,点开open的窗口会有错误:QWindowsWindow :: setGeometry: Unable to set geometry 100x30+530+225 on

回复
豆子 2014年7月16日 - 11:41

这个还不大清楚,不知道哪里的问题。要看具体的代码才好。

回复
aslistener 2015年7月1日 - 12:02

这个貌似不是错误, 而是 warning , 除非你的警告级别设高了。 这是因为 Widget 的“建议最大宽高”小于你设置的宽高。 可以 修改你的宽高参数, 也可以增大建议最大宽高。
http://home.cnblogs.com/u/aslistener/

回复
Ada 2015年8月27日 - 18:29

豆子老师,有什么办法可以在connect()调用之后把获得的值传到外部变量。

回复
豆子 2015年8月31日 - 22:18

如果一直持有指针,即可在有效范围内获取实际值了。

回复
豆子的小豆子 2015年9月5日 - 12:33

豆子大大,新手表示那个useragedialog类不知道怎么搞 QAQ

回复
豆子 2015年9月5日 - 21:57

UserAgeDialog 就是一个简单的对话框,上面只有一个 QTextLine 组件,用于接收用户输入的年纪。你可以尝试编写下这个对话框。

回复
江小白 2016年1月12日 - 22:51

豆子大神,单一的Qtextline组件不能显示在对话框上吧,得用QTextlayout才行啊,而且必须draw在某个QPainter上,不知道我说的对不对。

回复
豆子 2016年1月13日 - 22:11

可以使用 QLineEdit

回复
邓伟建 2015年11月23日 - 16:38

请问具体的UserAgeChanged这个函数是怎么写的呢?

回复
豆子 2015年11月23日 - 22:25

这是一个信号,不需要自己实现,moc 工具会帮助生成必须的代码。

回复
Jack 2016年2月15日 - 21:16

accept()什么时候调用呢?

回复
普法居士 2016年5月17日 - 13:09

点击dialog的确定按钮时候会调用accept()

回复
能否把思路尽量完整一些,片段只适合当自己的笔记,贴出来给新手看是让人走弯路白浪费精力的 2020年7月30日 - 11:33

哪里有确定按钮,dialog只有一个标题,一个?,和一个X

回复
妞妞 2016年5月25日 - 16:44

老师,你好,我想问一下,我要实现点击“读卡”按钮之后,它就开始读卡,这个怎么实现呢,或者,哪里有这样的学习资源?

回复
豆子 2016年5月26日 - 22:05

读卡作为一个独立的函数,在 SLOT 里面调用即可。这个函数的实现需要根据不同读卡系统有所不同

回复
妞妞 2016年5月27日 - 09:58

实现的主要区别在哪里?比如是用QT的某一个类还是怎么回事?

回复
ccppaa 2016年7月14日 - 11:28

大神,我现在在主页面有很多按钮,每一个按钮点击后弹出一个新的页面,例://快捷键
void MainDialog::shortcuts()
{ scDlg = new ShortcutsDialog(this);
scDlg->show();
scDlg->move(200,184);
}
然后有设置了快捷键调用,页面调用一次弹出一次,可以有什么方法限制吗,一次只能弹一个页面,

回复
ccppaa 2016年7月14日 - 15:53

上面这个解决了,用bool判断一下,但是有一个当作锁屏用的页面,因为他是在什么时候都能用,没办法用上面的办法,页面出来后,鼠标点击不能用,但是用快捷键调用他会重复出现,几个页面叠在一起

回复
豆子 2016年7月17日 - 19:29

这个你可以这么做:不要在 shortcuts() 中每次创建一个对话框,而是在构造函数中创建完毕之后,在 shortcut() 函数中只调用 show() 函数即可(可以使用 isVisible() 判断当前对话框是不是已经被显示出来)。

回复
ccppaa 2016年7月18日 - 08:35

好的,谢谢

回复
ccppaa 2016年7月28日 - 15:26

大神,因为新弹出的是当锁屏用的,怎么设置之前的页面不可用,现在是页面能正确的弹出来,用bool判断其它页面都不出现,if(dialogState)
{
dialogState=false;
lockDlg = new LockDialog(this);
lockDlg->show();
lockDlg->move(0,0);
if(lockDlg->exec()==QDialog::Rejected)
{
dialogState=true;
txmEdit->setFocus();
}
}

回复
豆子 2016年7月28日 - 18:06

没太明白你的意思。是不是这段代码需要放在所有页面中?

回复
ccppaa 2016年7月28日 - 18:10

当锁屏的时候设置主界面不可用,被锁屏dialog覆盖的maindialog不可用,解锁后才可用

豆子 2016年7月28日 - 18:20

是不是说你的锁屏对话框弹出来之后,虽然下面的界面不能用鼠标点击,但是快捷键还是可以用?如果是这样的话,试试设置 shortcutContext,将 shortcutContext 设置为 Qt::WidgetWithChildrenShortcut 之类,即当组件有焦点(默认是整个窗口有焦点)的时候才能使用快捷键。

cccccc 2016年8月1日 - 16:39

大神,快捷键是用event 和 keyevent设置的,该怎么设置他们可用或者不可用
if(keyEvent->key()==Qt::Key_F2)
{
txmEdit->setText("");
txmEdit->setFocus();
return true;
}这是设置的语句,在另外一个界面弹出的时候,按F2依然会执行,那个界面也会失去焦点,该怎么设置

回复
cccccc 2016年8月1日 - 18:18

之前形容的不够精确,是这样的在maindialog有个快捷键事件F2是让lineedit清空并聚焦,Ctrl+L弹出锁屏界面lockdialog,弹出语句:lockDlg->show(); lockDlg->move(0,0);锁屏界面弹出后焦点在lockdialog,这个时候如果按F2 maindialog的lineedit依然会聚焦,此时使用键盘事件已经无法操作lockdialog了,但是显示的还是lockdialog,lockdialog失去焦点了

回复
ccppaa 2016年8月2日 - 09:56

不好意思,麻烦大神了,有两条评论正在审核,这个问题我已经解决了,把他想复杂了

回复
bin 2016年9月7日 - 00:44

大神,考虑使用parent指针的方式构建对话框,避免设置WA_DeleteOnClose属性这句话是什么意思?具体怎么做呢?

回复
豆子 2016年9月8日 - 08:59

使用带有parent指针的方式创建对话框,例如dialog = new QDialog(this);这样,而不是dialog = new QDialog;,这样的话就不需要设置WA_DeleteOnClose了,因为有parent指针,Qt 会在销毁parent时自动销毁对话框

回复
Theodore A 2019年6月22日 - 11:27

自己遇到的一点坑,在这里给大家分享一下。
我按照豆子老师的思路新建了一个派生自QDialog的对话框类,然后在构建时一直卡在链接期,提示LINK2001错误,排查了很多遍都不知道哪里出了问题。最后是随手点了一下“执行qmake”解决了问题。如果也有人遇到了和我一样的问题,可以试一试执行qmake。我觉得应该是新建了类的文件后makefile需要更新的原因。如有谬误还请诸位大神指点。

回复
豆子 2019年6月23日 - 15:41

这个问题有可能是因为在创建类之后又添加了 Q_OBJECT 宏,如果是这样就必须先执行 qmake。因为普通的预处理不能正确处理 Q_OBJECT 宏。

回复
Kane 2020年7月29日 - 17:23

最后那个例子有点云里雾里的,想实现什么功能都没看出来,,,不如放个完整的伪代码上去

回复
Niuqiang 2021年7月9日 - 17:50

最后的例子是什么意思啊,大佬,刚开始学,看不懂

回复
豆子 2021年7月17日 - 15:03

最后的例子是重写 QDialog 的 accept() 函数,在其中发出信号,然后外面可以通过槽连接该信号,就可以在对话框外部拿到发出的值

回复
Luo 2021年11月1日 - 11:48

你重写为啥要在UserAgeDialog::accept()函数中最后加上这个呢QDialog::accept();?

回复
shayito 2021年8月20日 - 16:24

void UserAgeDialog::accept() 应该写在哪里,useragedialog.cpp吗,newage怎么传递?这个例子跑不通,求讲解qwq

回复

发表评论

关于我

devbean

devbean

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

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