7.0 KiB
7.0 KiB
#领域/未知
#复盘/0 #Bug/关闭
一句话描述
[开启软件总是提示上次文件____]
基础元信息
影响范围:[软件启动___]
缺陷记录
前置条件
[编程软件N46__]
复现步骤
- [打开主程序-1,关闭软件,期望下次开启无弹窗__]
- [关闭主程序-1,关闭软件,期望下次开启无弹窗__]
- [任务管理器关闭软件,期望下次开启有弹窗__]
实际发生结果
[总是出现弹窗__]
期望结果
[________]
排查建议
void MainWindow::closeEvent(QCloseEvent *event)
根因分析
[________]
验证依据
第1部分:变量声明与未保存检测(第462~469行)
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行)
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
三种分支的含义:
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行)
QString path_crash = QString("%1/system/data/crash_flag/crash_flag.txt")
.arg(QCoreApplication::applicationDirPath());
QFile file_crash(path_crash);
// ... 写入逻辑 ...
知识点 — QString 的 %1 占位符拼接:
QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath())
// ↑
// %1 会被替换成 arg() 的内容
// 假设 applicationDirPath() = "D:/Program/App"
// 结果: "D:/Program/App/system/data/crash_flag/crash_flag.txt"
这比 C 风格的字符串拼接更安全、更可读:
// ❌ C风格(容易出错):
QString path = QCoreApplication::applicationDirPath() + "/system/data/...";
// ✅ Qt风格(推荐):
QString path = QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath());
知识点 — QTextStream 写文件:
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):
crash_list.chop(1); // 从末尾删掉1个字符
用来去掉循环中最后一次多余的 \n 换行符。等价于原来的 .remove(size-1, 1) 但更语义化。
第4部分:关闭所有标签页(第525~527行)
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行)
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行)
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) |