--- #领域/未知 #复盘/0 #Bug/关闭 ## 一句话描述 [__开启软件总是提示上次文件______] --- ## 基础元信息 影响范围:[__软件启动_____] ## 缺陷记录 ### 前置条件 [___编程软件N46_____] ### 复现步骤 1. [___打开主程序-1,关闭软件,期望下次开启无弹窗_____] 2. [___关闭主程序-1,关闭软件,期望下次开启无弹窗_____] 3. [___任务管理器关闭软件,期望下次开启有弹窗_____] ### 实际发生结果 [___总是出现弹窗_____] ### 期望结果 [________] ### 排查建议 void MainWindow::closeEvent(QCloseEvent *event) ### 根因分析 [________] ### 验证依据 ## 第1部分:变量声明与未保存检测(第462~469行) ```cpp bool has_unsaved = false; bool user_discard_unsaved = false; // 用户主动选择不保存 for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i).contains("*")) { has_unsaved = true; break; } } ``` **知识点:** |代码|含义| |---|---| |`ui->tabWidget->count()`|获取标签页总数量| |`ui->tabWidget->tabText(i)`|获取第 i 个标签页的显示文字| |`.contains("*")`|检查字符串是否包含 `*`(未保存标记)| |`break`|找到第一个就够了,不用继续遍历| **为什么用两个变量?** - `has_unsaved` = "有没有带星号的标签"(客观事实) - `user_discard_unsaved` = "用户是不是主动说不保存了"(用户意图) - 两者组合才能判断最终是否要记录 crash --- ## 第2部分:弹窗与用户选择处理(第471~496行) ```cpp if (has_unsaved) { int result = QMessageBox::information( NULL, tr("提示"), tr("正在关闭软件,是否保存当前文件?"), QObject::tr("保存"), // 返回值 0 QObject::tr("不保存"), // 返回值 1 QObject::tr("取消") // 返回值 2 ); // ... 三种分支 ... } ``` **知识点 — `QMessageBox::information` 的返回值:** 这个函数有3个按钮,返回值就是用户点击的第几个(从0开始数): ``` ┌─────────────────────────────┐ │ 提示 │ │ 正在关闭软件,是否保存当前文件? │ │ │ │ [保存] [不保存] [取消] │ └─────────────────────────────┘ ↑ ↑ ↑ result=0 result=1 result=2 ``` **三种分支的含义:** ```cpp if (result == 0) { // 保存 → 调用保存函数,文件写磁盘 for (...) { on_actionsave_triggered(); } } else if (result == 1) { // 不保存 → 标记一下,但不是异常 user_discard_unsaved = true; } else if (result == 2) { // 取消 → 阻止窗口关闭! event->ignore(); return; // 直接退出函数,后面都不执行了 } ``` **核心知识点:`event->ignore()` vs `event->accept()`** |方法|效果|类比| |---|---|---| |`event->accept()`|允许关门|说"好的,可以关了"| |`event->ignore()`|拒绝关门|说"等等,先别关!"| |不调用任何|默认行为取决于控件|窗口默认是 accept| --- ## 第3部分:写入 crash 标志(第498~523行) ```cpp QString path_crash = QString("%1/system/data/crash_flag/crash_flag.txt") .arg(QCoreApplication::applicationDirPath()); QFile file_crash(path_crash); // ... 写入逻辑 ... ``` **知识点 — QString 的 `%1` 占位符拼接:** ```cpp QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath()) // ↑ // %1 会被替换成 arg() 的内容 // 假设 applicationDirPath() = "D:/Program/App" // 结果: "D:/Program/App/system/data/crash_flag/crash_flag.txt" ``` 这比 C 风格的字符串拼接更安全、更可读: ```cpp // ❌ C风格(容易出错): QString path = QCoreApplication::applicationDirPath() + "/system/data/..."; // ✅ Qt风格(推荐): QString path = QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath()); ``` **知识点 — QTextStream 写文件:** ```cpp QTextStream crashStream(&file_crash); // 把文件绑定到文本流 crashStream.setCodec("gbk"); // 设置编码(中文需要) crashStream << crash_list; // 用 << 操作符写入(类似 cout) file_crash.close(); // 关闭文件(flush缓冲区到磁盘) ``` **crash 标志的两种内容:** |条件|写入内容|含义|示例| |---|---|---|---| |`has_unsaved && !user_discard_unsaved`|`"1\n/path/a.nyt\n/path/b.nyt"`|异常退出,记录路径供恢复|下次启动会弹出"是否重新打开"| |其他情况|`"0"`|正常退出|下次启动不会提示| **知识点 — `chop(1)`:** ```cpp crash_list.chop(1); // 从末尾删掉1个字符 ``` 用来去掉循环中最后一次多余的 `\n` 换行符。等价于原来的 `.remove(size-1, 1)` 但更语义化。 --- ## 第4部分:关闭所有标签页(第525~527行) ```cpp while (ui->tabWidget->count() > 0) { removeSubTab(0); // 始终删除索引0 } ``` **为什么每次都删索引0?** 因为 `removeSubTab` 删除一个 tab 后,后面的所有 tab 索引都会 **前移一位**: ``` 初始状态: [TabA] [TabB] [TabC] count=3 remove(0)→ [TabB] [TabC] count=2, 原来的TabB变成索引0 remove(0)→ [TabC] count=1, TabC变成索引0 remove(0)→ (空) count=0, 结束循环 ``` 如果从后往前删也可以,但从前往删最简洁,不需要计算动态变化的索引。 --- ## 第5部分:写入历史记录(第529~544行) ```cpp QString history_str = ""; for (int i = 0; i < str_history_list.size(); i++) { history_str.append(str_history_list.at(i)); if (i != str_history_list.size() - 1) { history_str.append("\n"); // 最后一条不加换行 } } ``` **这段做了什么?** 把内存中的历史列表(`str_history_list`)拼成一个字符串写入文件,这样下次打开软件还能看到最近打开过的文件。 --- ## 第6部分:接受关闭事件(第546行) ```cpp event->accept(); ``` **这是整个函数唯一的出口**(除了 `return` 的取消分支)。执行到这里意味着: - 用户没有点"取消" - crash 标志已写好 - 标签页全部关掉了 - 历史记录已保存 - **正式允许窗口关闭** --- ## 总结:你学到的 Qt/C++ 模式 |模式|在哪用的|记忆口诀| |---|---|---| |**遍历检测**|第1部分|遍历 + break(找到就停)| |**三按钮弹窗**|第2部分|返回值 0/1/2 对应按钮顺序| |**事件拦截**|第2部分|ignore = 别关 / accept = 可以关| |**%1 占位符**|第3部分|`"%1/...".arg(变量)` 比 `+` 更清晰| |**QTextStream 写文件**|第3部分|绑定 → setCodec → `<<` 写入 → close| |**前向删除法**|第4部分|while 删索引0,自动补位| |**单出口原则**|第6部分|函数只有一个 accept 出口(除了错误return)|