juechafun/01-项目/2604-编程软件/Bug-编程软件-软件启动时弹窗.md

256 lines
7.0 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
#领域/未知
#复盘/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|