资讯动态

Qt模态与非模态对话框:事件循环、生命周期与避坑指南

发布时间:2026/10/17 11:50:06 来源:尧图企业网站定制
模态对话框和非模态对话框这东西几乎每个用Qt写过界面的人都会碰到。但老实说很多人用了很久也只是停留在“知道有这俩东西”的程度一个会卡住界面一个不会。至于底层的事件循环机制、生命周期管理、为什么用exec()会进入局部事件循环、为什么new出来的非模态窗口有时候一闪就没这几个问题一问能讲清楚的人其实不多。这篇文章不打算从零开始讲Qt是什么直接围绕模态与非模态对话框把原理、写法、坑和排查思路一次性理清楚。代码基于Qt Widgets模块环境是Qt 5.15和Qt 6.x都通用文中提到的地方我会单独说明。1. 模态对话框与非模态对话框的核心区别1.1 两者的本质差异不是一个卡界面一个不卡这么简单很多人第一反应是模态对话框会阻塞程序非模态不会。这个说法方向对但表述不准确容易误导。模态对话框真正做的事情是启动一个嵌套的局部事件循环并且限制用户只能与该对话框交互。在关闭这个对话框之前它后面的主窗口是拿不到任何鼠标和键盘事件的——不是单纯“卡住”而是事件被拦截了。这个局部事件循环和主事件循环是独立的也就是说窗口照常绘制、定时器照常触发、网络事件照常处理只是用户无法操作其他窗口。非模态对话框则相反。它和主窗口共享同一个事件循环调用show()之后函数立刻返回程序继续往下执行。用户可以在非模态对话框和主窗口之间来回切换操作互不阻塞。理解这两个概念要抓三个关键点事件循环模态对话框用的是独立的局部事件循环非模态对话框用的是主事件循环。阻塞范围模态阻塞的是用户对底层窗口的交互不是阻塞整个程序的所有事件处理。生命周期模态对话框一般用栈对象或exec()方式就够了非模态对话框必须注意生存期否则会出现一闪而没的现象。1.2 实际场景中的选择依据场景推荐类型原因文件打开/保存模态用户完成选择之前不应该操作主窗口设置对话框模态设置项需要一次性确认避免中间状态警告/确认提示模态强制用户确认防止遗漏关键操作查找/替换窗口非模态需要同时操作主窗口和查找窗口工具箱/调色板非模态常驻辅助窗口随用随取进度条窗口非模态主窗口要保持响应用户可取消或后台操作判断的核心依据就一条用户在操作这个对话框期间是否需要同时操作主窗口。需要就非模态不需要就模态。2. 模态对话框的三种实现方式与原理拆解2.1 最常用的方式exec()局部事件循环void MainWindow::onOpenDialog() { QDialog dlg(this); if (dlg.exec() QDialog::Accepted) { // 用户点击了“确定”执行后续逻辑 } }这是最常见的写法。dlg是栈对象exec()启动局部事件循环代码在这里停住直到对话框被关闭。关闭后exec()返回根据返回结果判断用户是确认还是取消。整个过程中主窗口的事件循环没有被销毁只是暂时进入嵌套状态。Qt会把主窗口的交互事件拦截掉但定时器、QTimer、网络回复、重绘事件都还在正常跑。这一点特别重要因为在对话框里启动耗时操作时主窗口看起来“没死”只是用户点不了。exec()内部源码会调用QEventLoop并设置窗口模态属性。从Qt 5开始exec()内部已经做了多次重入保护如果你在按钮槽函数里再次调用exec()它是允许的会形成更深一层的嵌套事件循环。但绝不建议这么写一旦某个分支忘掉关闭路径整个调用栈就乱套了。2.2 用open()实现窗口级模态void MainWindow::onOpenDialog() { QDialog *dlg new QDialog(this); dlg-setWindowModality(Qt::WindowModal); dlg-setAttribute(Qt::WA_DeleteOnClose); dlg-open(); }open()和exec()的区别在于open()不进入新的阻塞事件循环它设置了窗口模态属性之后直接显示窗口并立即返回。窗口会限制输入但函数不卡住。这种方式适合需要模态限制但你又不想反复构造析构对话框的场景。比如一个需要多次弹出的配置窗口每次用open()打开同一个实例就行不会产生exec()嵌套调用的问题。Qt::WindowModal表示模态范围仅限于该窗口所在的窗口层级即只阻塞父窗口及其子窗口。Qt::ApplicationModal则会阻塞应用中所有窗口。QDialog的exec()默认就是ApplicationModal级别的。2.3 栈对象vs堆对象的生命周期陷阱模态对话框用栈对象没问题因为exec()阻塞期间对象一定存活。函数执行到return之前对话框已经关闭栈对象析构安全。核心原因就是exec()把执行流卡住了函数栈没有被回收。但如果你试图这样写一个非模态对话框问题就来了void MainWindow::onShowDialog() { QDialog dlg(this); // 错误示范不要这样写 dlg.show(); // show()立即返回 }show()执行完函数立刻结束栈上的dlg马上就析构了窗口在屏幕上可能闪一下甚至根本看不见。这就是典型的“一闪而没”。解决方案是用new分配到堆上并加上Qt::WA_DeleteOnClose让窗口关闭时自动delete。注意如果父对象是this主窗口其实窗口销毁时父对象也会销毁子对象所以很多人只写new QDialog(this)而不加WA_DeleteOnClose这种写法不会内存泄漏但每次打开都new一个窗口会出现多个实例叠加。具体怎么做后面第3节详细展开。2.4 setModal()与setWindowModality()区别在哪setModal(true)是QDialog自己提供的方法它设置的是窗口的模态属性同时影响了show()和exec()两个路径的行为。如果先setModal(true)再调用show()这个show()的情感和exec()就有点像了——窗口会以模态方式显示show()仍然立即返回但输入被限制在对话框内。区别在于exec()还会进入局部事件循环而show()不会。setWindowModality(Qt::WindowModal)是QWidget层面的通用接口不局限于QDialog。你用QMainWindow也可以设置窗口模态配合show()实现类似效果。做法总结exec()进入局部事件循环阻塞适合一次性使用。show() setModal(true)不阻塞线程但限制输入范围。open()一次性显示模态窗口内部基于show()实现但处理了窗口状态问题。3. 非模态对话框的创建与管理3.1 标准写法new show 防止多开void MainWindow::onShowFindDialog() { if (!m_findDialog) { m_findDialog new FindDialog(this); m_findDialog-setAttribute(Qt::WA_DeleteOnClose); } m_findDialog-show(); m_findDialog-raise(); m_findDialog-activateWindow(); }m_findDialog是MainWindow的成员变量初始为nullptr。这样写的好处有三点窗口只创建一次之后每次只是显示和激活。WA_DeleteOnClose保证关闭时自动释放内存。如果不加m_findDialog存在性判断多次点击按钮会弹出多个重复窗口这是个很常见的bug。raise()和activateWindow()这两个调用很多人会漏。第一次show()时窗口默认会获得焦点并置顶但用户把窗口最小化或点到后面去了之后再次点击按钮如果没有raise和activateWindow你会感觉按钮点了没反应——其实窗口在任务栏上等着只是没有被带到前台。3.2 WA_DeleteOnClose的底层行为这个属性本质上是在QWidget的closeEvent里做了一件额外的事把deleteLater()投递到事件循环窗口隐藏后在合适时机销毁。它不是立即delete的是延迟销毁。这个设计很聪明。如果你在某个槽函数里对象A的closeEvent触发deleteLater而当前函数还在执行如果立即delete对象后面代码再用到这个对象指针就会崩溃。延迟销毁保证当前事件处理完之前对象是安全的。使用WA_DeleteOnClose后访问已关闭窗口的指针就成了悬垂指针// 别这么做 m_findDialog-close(); delete m_findDialog; // 如果已设置WA_DeleteOnClose这里会双重释放直接崩溃正确做法是如果设置了WA_DeleteOnClose就不要再手动delete。判断对话框是否已被关闭可以重写closeEvent或使用QPointer来安全持有指针。QPointer是Qt提供的一个安全指针包装指向的QObject被删除后QPointer会自动变成nullptrQPointerFindDialog m_findDialog; void MainWindow::onShowFindDialog() { if (m_findDialog.isNull()) { m_findDialog new FindDialog(this); m_findDialog-setAttribute(Qt::WA_DeleteOnClose); } m_findDialog-show(); m_findDialog-raise(); m_findDialog-activateWindow(); }用QPointer存QDialog指针可以避免“窗口关闭后再访问指针”的悬垂问题。3.3 非模态对话框的数据回传方式非模态对话框关闭后主窗口怎么知道用户做了什么不能像exec()那样直接拿返回值。需要换成信号槽或者回调。常用的做法是对话框内部定义信号class FindDialog : public QDialog { Q_OBJECT public: explicit FindDialog(QWidget *parent nullptr); signals: void findNext(const QString text, bool caseSensitive); };在按钮点击的槽函数里发射信号void FindDialog::onFindClicked() { emit findNext(m_lineEdit-text(), m_caseCheckBox-isChecked()); }主窗口连接这个信号connect(m_findDialog, FindDialog::findNext, this, MainWindow::findNext);connect在MainWindow构造函数或首次创建对话框时只连接一次。这样每次窗口即使重复显示信号连接不会叠加。特别强调不要在show()之前每次都connect一次否则槽会被多次触发这是很多人都踩过的坑。3.4 非模态对话框的关闭行为定制默认情况下点击标题栏关闭按钮窗口会隐藏而不是销毁前提是没有WA_DeleteOnClose。对于工具箱窗口这个行为其实是合适的。用户关闭之后再次打开窗口内的输入内容可能还保留着体验更自然。但有些场景希望关闭即清理比如一次性的临时对话框关闭后所有状态都重置。那就加WA_DeleteOnClose。两者没有绝对优劣看业务需求。还可以重写closeEvent拦截关闭void FindDialog::closeEvent(QCloseEvent *event) { if (m_hasUnfinishedWork) { int ret QMessageBox::warning(this, 提示, 还有未完成的任务确定关闭吗, QMessageBox::Yes | QMessageBox::No); if (ret ! QMessageBox::Yes) { event-ignore(); return; } } event-accept(); }在写这个逻辑时有一个易错点如果设置了WA_DeleteOnClosecloseEvent返回后对象就排队准备销毁。不要再继续访问this的任何成员否则属于典型的use-after-free。4. 标准对话框在工作中的实际封装经验4.1 QMessageBox的使用误区QMessageBox::question(this, 确认, 确定删除吗);这种写法能跑但存在两个问题question返回的是StandardButton整型值不判空就直接用可能导致逻辑错误二是连父窗口都没传弹窗层级会乱。正经写法QMessageBox::StandardButton ret QMessageBox::question( this, 确认删除, 确定要删除这条记录吗此操作不可恢复。, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret QMessageBox::Yes) { // 执行删除 }最后一个参数defaultButton设为No这样用户按回车时默认选一个安全选项避免误删。这个细节很多商用软件都做得不好。QMessageBox有exec()、show()两种用法。show()方式是非模态的适合“刷新完成”这类不需要回执的提示。但QMessageBox的show()有一个经典坑在show()之后如果不再持有对象引用窗口会一闪而过甚至直接崩溃。需要new到堆上QMessageBox *msg new QMessageBox(QMessageBox::Information, 完成, 刷新完成, QMessageBox::Ok, this); msg-setAttribute(Qt::WA_DeleteOnClose); msg-show();4.2 QFileDialog的模态封装文件对话框通常做成模态的但注意一点在exec()之前设置选项否则某些选项可能不生效。QString filePath QFileDialog::getOpenFileName( this, 选择配置文件, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), Config Files (*.ini *.conf);;All Files (*));这个静态函数内部封装了模态对话框的完整生命周期用起来最省心。但它有阻塞风险如果函数在主线程调用而主线程还有未处理的定时任务这些任务在exec()期间不被调度表现为某些UI操作延迟。所以文件对话框不要频繁弹最好由用户主动触发。4.3 常用输入的封装QInputDialogbool ok false; QString text QInputDialog::getText( this, 输入名称, 请输入新名称, QLineEdit::Normal, QString(), ok); if (ok !text.isEmpty()) { // 处理输入 }getText系列返回字符串是否点了取消要通过ok标志位判断。不判断ok直接拿text判断空字符串这两种判断有语义差异用户清空输入点击确定和点击取消在返回值上有区别要注意区分。4.4 自定义模态对话框的推荐模板日常开发我会维护一个通用的模态对话框模板主要处理了一个问题模态对话框里有一个“应用”按钮点击后不关闭窗口只向主窗口发送信号。这种情况下用exec()就会卡住无法连续操作。class SettingsDialog : public QDialog { Q_OBJECT public: explicit SettingsDialog(QWidget *parent nullptr); private slots: void onApply(); void onOk(); signals: void applied(); void acceptedAndApplied(); };实现时让“应用”按钮emit applied()但不关闭窗口“确定”按钮emit acceptedAndApplied()并执行accept()。这种模板最实用的一点是当用户点“应用”时数据已经被发送到业务层即使点“取消”关闭窗口应用过的配置也是生效的。很多软件就是这样设计的。5. 事件循环视角下的深入理解5.1 事件循环和exec()的关系Qt程序启动时main函数里app.exec()启动了主事件循环。这个循环负责分发所有事件鼠标、键盘、绘制、定时器、socket。当你调用dialog.exec()时Qt内部启动了第二个事件循环和主事件循环是嵌套关系。这个内层循环分发对话框窗口范围内的事件以及之前提到的各种非用户交互事件。用户在对话框上点击确定或取消触发accept()或reject()内部调用close()最后退出这个局部事件循环回到exec()调用点。这个机制解释了很多现象。为什么模态对话框弹出后主窗口还能重绘因为局部事件循环仍然在处理QPaintEvent。为什么模态对话框弹出后主窗口的按钮点击没反应因为局部事件循环对主窗口的鼠标事件做了拦截。理解嵌套事件循环难点在于重入。比如模态对话框里弹出一个二级模态对话框局部事件循环里再嵌一层局部事件循环。每一层都非常消耗理解力调试时也更复杂。第一级没有清理完就弹出第二级可能出现首次accept之后第一级也没了反应的诡异状态。规规矩矩用exec()没问题但如果结合open()和show()又要加setModal()组合多了容易出这种重入问题。5.2 为什么说show() exec()混用很危险show()和exec()不能混用在同一个窗口上。先show()再exec()exec()内部会改变窗口的模态状态前面show()的状态可能被覆盖窗口表现不可控。更危险的是exec()之后又show()。exec()已经让窗口进入了模态局部事件循环状态弹出的窗口关闭后会回到主事件循环。如果此时又调用show()窗口会以非模态方式显示但用户以为它还是模态的操作了主窗口。逻辑混乱不建议在实际项目里出现这种代码。5.3 非模态对话框和线程的关系非模态对话框不能跨线程直接操作。QWidget要生活在创建它的线程里。某些耗时任务在主线程执行会导致UI卡顿常用方案是扔到工作线程。但工作线程产生的数据要回到主线程再更新UI。非模态对话框的标准更新路径是信号槽即工作线程处理完成后发信号主线程槽函数里更新对话框内控件。注意connect时要选择Qt::QueuedConnection或使用默认的AutoConnection跨线程时会自动变为队列连接。有一种隐含的错误用法是在工作线程里直接调用非模态对话框控件的setText等方法。即使从纯技术上偶然不崩溃这个东西也是不确定行为。Qt文档明确表示UI操作必须在GUI线程执行。避免这个问题最简单的办法是确保所有对话框都在主线程创建销毁工作线程通过信号槽回到主线程。5.4 main()事件循环崩溃的两种常见现场现场一main()里首次创建QDialog还没exec()就退出了。比如int main(int argc, char *argv[]) { QApplication app(argc, argv); QDialog dlg; dlg.show(); // 忘写 app.exec(); return 0; }窗口一闪就退出有时甚至半个影子都没看到。这不是show()的问题是主事件循环没运行。show()只是设置窗口可见标志真正的事件分发靠app.exec()。现场二在MainWindow构造函数里弹模态对话框。比如MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMessageBox::information(this, 提示, 正在初始化...); }构造还没完成就进入新的局部事件循环用户点击后返回构造函数继续往下执行。看起来没什么问题但父窗口都还没构造完对话框的parent已经传了this此时this很可能还不是合法窗口。某些平台上会出现对话框显示异常或者主窗口构造完成后闪烁。正确做法是把这种启动提示放到showEvent或某个初始化定时器里等窗口完全构造好再弹。6. 常见问题与排查技巧实录6.1 非模态对话框一闪而过这是网上被问烂的问题同时也是每个新手都会踩的坑。根因就一个对象被析构了。排查顺序看对话框是在哪里创建的。如果是函数局部变量show()之后函数返回就销毁一定会闪。改成堆对象或成员变量。看是否设置了WA_DeleteOnClose。如果设置了且对话框被close过一次指针已经失效。使用QPointer重新判断。看parent是否传错。如果将父对象设为一个局部变量父对象先于对话框被析构子窗口也就没了。6.2 模态对话框打不开或者打开了但点不了按钮多级窗口模态冲突会导致这种问题。比如A窗口是ApplicationModal模态B窗口也是通过setWindowModality(Qt::ApplicationModal)再show()出来的嵌套非模态窗口。如果B把A的事件过滤掉了A上的模态对话框就永远无法响应。排查的时候不要只看当前窗口要把整个窗口树打印出来看父子关系和模态属性。6.3 对话框关闭后程序无法退出这种问题的典型特征是主窗口关闭了进程还留在任务管理器里得强制结束。常见原因是有个非模态对话框还开着它的事件循环还挂在外面。主窗口销毁的时候它的成员对话框如果没设置WA_DeleteOnClose且父对象不是主窗口就会残留。排查方式看看程序退出路径上有没有show()出来的独立对话框没关。在主窗口closeEvent里把所有非模态对话框都close()掉再调用accept()可以避免大部分这种问题。void MainWindow::closeEvent(QCloseEvent *event) { if (m_findDialog) { m_findDialog-close(); } if (m_toolboxDialog) { m_toolboxDialog-close(); } event-accept(); }6.4 按钮绑定槽函数触发多次一般来说是因为重复connect。每次打开对话框时执行了一次connect而对话框是在堆上、关闭时不销毁没加WA_DeleteOnClose再次show()后信号被多次connect触发一次按钮、执行多次槽。解决办法把connect放在创建对话框的代码里且只创建一次。如果是connect到lambda很多团队会直接建议禁止在connect中写复杂业务逻辑原因就是调试难。6.5 模态对话框不阻塞主窗口却阻塞了任务有一种特殊情况如果popup窗口如QComboBox下拉框、QMenu处于打开状态此时在主线程弹出一个模态对话框底层会因为窗口层级关系表现出“主窗口依然可以操作”或“模态失效”的现象。优先关闭所有popup再弹模态框。类似QComboBox的下拉列表在弹框前要通过view()-viewport()-close()等方式先手动关掉否则在部分平台的表现是弹出窗口叠在下拉框下面看起来模态失效。6.6 设置对话框的父子窗口层级混乱对话框显示在主窗口后面有时候是parent传错了有时候是窗口标志覆盖了层级。如果设置了Qt::WindowStaysOnBottomHint或Qt::FramelessWindowHint这些标志模态和非模态的行为会变。特别常见于自定义标题栏的对话框丢失了系统级层级恢复模态能力需要自己处理事件过滤。这个场景我的建议是自定义标题栏窗口不要轻易动windowFlags确实需要无边框窗体时自己用installEventFilter监听焦点变化、临近窗口切换等事件手动维护层级。6.7 快速点击造成对话框重复弹出这种情况是show之前没有判断窗口是否已经在显示状态。即使加了成员变量判断也存在一个竞态第一次点击时m_findDialog为空创建了一个窗口并显示窗口关闭后m_findDialog并没有变为null因为QPointer才能自动感知对象销毁此时如果直接把成员变量清空逻辑写在closeEvent里再次点击时才会正常重新创建。一个稳妥做法connect(m_findDialog, QDialog::finished, this, [this]() { m_findDialog nullptr; });finished信号在窗口关闭后被发射这个lambda将成员指针清空。下次点击自然重新创建。注意如果同时设置了WA_DeleteOnClosefinished之后对象马上会delete此时lambda中访问成员变量是安全的把指针置空恰好解决悬垂问题。但要小心不要在finished里访问对话框对象本身因为对象即将销毁。6.8 exec()嵌套调用导致内存泄漏这种情况多数发生在把对话框new到堆上又没设置WA_DeleteOnClose的场景// 错误示范 void MainWindow::onButtonClicked() { QDialog *dlg new QDialog(this); dlg-exec(); delete dlg; }exec()阻塞时delete dlg代码根本没执行如果此时用户点击关闭按钮exec()返回delete执行看起来可以。但如果在exec()运行期间用户去操作主窗口以外的东西其实阻塞了。真正容易漏的是exec()内部在关闭时因为某些原因提前return比如窗口关闭事件被ignore或者对话框没有exec()就销毁delete永远不会执行。更稳的写法是不手动delete用WA_DeleteOnCloseQDialog *dlg new QDialog(this); dlg-setAttribute(Qt::WA_DeleteOnClose); dlg-exec();这样关闭时总是自动销毁不手动管理。6.9 模态对话框按钮点击无反射的问题对话框按钮点击后界面没反应但是逻辑执行了这种情况多半是阻塞住了UI刷新或者说exec()嵌套层数太深导致事件积压。当深层次modal的退出流程没走对底层的重绘事件会延迟刷新。可以先确认按钮对应的槽函数运行时长超过100毫秒级就要考虑在槽函数里只做数据收集把耗时逻辑放到异步流程中。6.10 Qt 6.x下的兼容性调整Qt 6中QDialog和QMessageBox的基本用法没有变但有一个影响较大的变化不再支持setDefault()-defaultButton()的旧接口链需要在实例化QMessageBox时指定defaultButton或者在button(QMessageBox::No)上调用setDefault()。还有Qt 6中高DPI缩放行为改用了新的系统机制模态对话框的大小策略会受屏幕比例影响在缩放比例较大的平台上对话框编辑器里设置的最小尺寸可能会被缩放。这时要使用sizeHint结合minimumSize来控制实际大小不要让对话框过于依赖系统默认缩放。7. 综合示例一个带查找、替换、设置功能的窗口最后给出一个综合示例把这些知识点串起来。做一个简单的文本编辑器主界面含查找对话框非模态、替换对话框非模态、设置对话框模态。#include QMainWindow #include QDialog #include QLineEdit #include QTextEdit #include QPushButton class FindDialog : public QDialog { Q_OBJECT public: explicit FindDialog(QWidget *parent nullptr); signals: void findNext(const QString text, bool caseSensitive); }; FindDialog::FindDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(查找); // 创建控件... connect(m_findButton, QPushButton::clicked, this, [this]() { emit findNext(m_lineEdit-text(), m_caseCheck-isChecked()); }); }主窗口class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent nullptr); private slots: void openFindDialog(); void openSettingsDialog(); private: QTextEdit *m_textEdit; QPointerFindDialog m_findDialog; }; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_textEdit(new QTextEdit(this)) { setCentralWidget(m_textEdit); } void MainWindow::openFindDialog() { if (m_findDialog.isNull()) { m_findDialog new FindDialog(this); m_findDialog-setAttribute(Qt::WA_DeleteOnClose); connect(m_findDialog, FindDialog::findNext, this, [this](const QString text, bool cs) { m_textEdit-find(text, cs ? QTextDocument::FindCaseSensitively : QTextDocument::FindFlags()); }); } m_findDialog-show(); m_findDialog-raise(); m_findDialog-activateWindow(); } void MainWindow::openSettingsDialog() { QDialog settings(this); // 假设放几个设置项 if (settings.exec() QDialog::Accepted) { // 应用设置 } }主窗口关闭时void MainWindow::closeEvent(QCloseEvent *event) { if (m_findDialog) { m_findDialog-close(); } event-accept(); }m_findDialog用的是QPointer窗口关闭销毁后自动置空。connect只执行一次重复打开窗口不会出现槽函数多次触发的问题。这是经过多次踩坑之后我沉淀下来的一个标准写法。模态对话框选择exec()一次性使用非模态用QPointer持有并常驻复用。如果非模态对话框的内容很简单临时用一下用new WA_DeleteOnClose也够。如果是“工具箱”型窗口建议成员变量持有不要销毁下一次打开直接show()还能保留用户上次的状态。我个人实际使用中还有一个心得不要试图把模态对话框的exec()返回值当唯一结果。很多业务逻辑其实更适合用信号来做因为信号天然支持异步方便解耦也比“返回值判断”更容易扩展。你一开始用信号槽方式做对话框通信后面要改成异步逻辑会非常平滑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑