256 lines
7.0 KiB
Markdown
256 lines
7.0 KiB
Markdown
|
||
---
|
||
#领域/未知
|
||
|
||
#复盘/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)|
|
||
|
||
|