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

7.0 KiB
Raw Blame History


#领域/未知

#复盘/0 #Bug/关闭

一句话描述

[开启软件总是提示上次文件____]


基础元信息

影响范围:[软件启动___]

缺陷记录

前置条件

[编程软件N46__]

复现步骤

  1. [打开主程序-1关闭软件期望下次开启无弹窗__]
  2. [关闭主程序-1关闭软件期望下次开启无弹窗__]
  3. [任务管理器关闭软件,期望下次开启有弹窗__]

实际发生结果

[总是出现弹窗__]

期望结果

[________]

排查建议

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