Files
scuc-qt-course/docs/teaching/slides/day5.html
T
张宗平 0f81edcc74 幻灯片按 4 天版重排:Day1 全 C++、Day3 启用 ch05/ch06、Day5 综合案例
- Day1:开训 + 全天 C++(PRETEST 摸底环节内联;★所有权/★编译 vs 链接错误新卡)
- Day2:Qt 入门 + 信号槽 → 计算器(★栈控件双重释放/★lambda 第五参数/
  ★重复 connect/★QString 中文编码,均链到坑点复现示例)
- Day3:QMainWindow/QDialog(p03/ch05、ch06 首次排课,☆两张新卡)+ 布局/控件/QSS
- Day4:事件/绘图/QTableWidget + 文件系统/JSON(★工作目录/★文件编码)
- Day5:综合案例全天(student_manager 走查 + QThread 提升 + 流程规范)
- index.html 同步 4+1 结构;相对链接脚本校验 0 失效
2026-07-05 12:49:44 +08:00

231 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Day 5 · 综合项目案例讲解:学员信息管理器</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="reveal/reveal.css">
<link rel="stylesheet" href="reveal/theme.css">
<link rel="stylesheet" href="reveal/plugin/highlight/monokai.css">
<link rel="stylesheet" href="custom.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>Day 5</h1>
<h3>综合项目案例讲解:学员信息管理器(全天)</h3>
<p class="module-badge">C++/Qt 方向实训 · 综合案例日 · 明天起进入项目开发</p>
<ul>
<li>上午:需求拆解 → 界面骨架 → 数据层(Day1–4 全部知识点的收束)</li>
<li>下午:提升——QThread 防卡界面;开发流程与规范;分组定项目方向</li>
</ul>
<p><small>目标:通过一个完整小软件讲清 Qt 开发的<b>流程、规范及方法</b>——
项目 = 已学知识点的组装。参考实现:
<a class="filelink" target="_blank" href="../examples/student_manager/qt_student_manager.cpp">examples/student_manager/</a>
<a class="filelink" target="_blank" href="../examples/student_manager/README.md">README</a>)。
Day6–9 学员项目开发,Day10 下午答辩。</small></p>
</section>
<!-- ===================== 上午 1:需求与骨架 ===================== -->
<section>
<section>
<p class="module-badge">上午 1 · 需求拆解 → 界面骨架</p>
<h2>从需求到窗口 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>需求拆解:先想清楚再动手 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念——开发流程的第一步不是写代码</h4>
<ul>
<li>需求:管理学员记录(姓名/成绩/标签),支持添加、保存、读取、批量导入。</li>
<li>拆功能 → 映射到已学组件:列表展示=QTableWidget(Day4)、添加=模态对话框(Day3)、
保存读取=QFile+JSON(Day4)、批量导入=后台线程(今天新授)、外壳=QMainWindow(Day3)。</li>
<li>拆完就得到任务清单——这正是 Day6 起你们在 Jira 上要做的事。</li>
</ul>
</div>
</section>
<section>
<h3>界面骨架走查 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>回指 Day34</h4>
<ul>
<li>QMainWindow:菜单(文件/记录/帮助)+ 工具栏 + 状态栏 + <code>setCentralWidget(table)</code></li>
<li>QAction 一个动作三处复用(菜单/工具栏/快捷键),<b>每个 action 都要 connect</b></li>
<li>添加记录:模态 <code>exec()</code> + <code>QFormLayout</code> + <code>QDialogButtonBox</code></li>
<li>表格 item 所有权:new 出来交给表格——父子所有权模型第 N 次出现。</li>
</ul>
</div>
<p><small>现场跑:<code>tools/run_qt.sh qt_student_manager</code>(离屏自测版:
<code>QT_QPA_PLATFORM=offscreen</code>,自动走完整验证流程)</small></p>
</section>
</section>
<!-- ===================== 上午 2:数据层 ===================== -->
<section>
<section>
<p class="module-badge">上午 2 · 数据层</p>
<h2>记录 ↔ JSON ↔ 磁盘 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>数据层走查 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
struct Student { QString name; double score; QString tag; };
QJsonObject toJson(const Student&); // 互逆的一对
Student fromJson(const QJsonObject&); // round-trip = 正确性标准
</code></pre>
<div class="concept-box">
<h4>回指 Day4(昨天作业的完整版)</h4>
<ul>
<li>数据结构与界面分离:<code>Student</code> 结构体不知道 QTableWidget 的存在——
这是 Model/View 思想的朴素版;</li>
<li>健壮性三处检查全部在场:<code>open()</code> 返回值、<code>QJsonParseError</code>
路径用 <code>applicationDirPath()</code> 拼。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>复盘昨天作业的常见问题</h4>
<ul>
<li>保存和读取的字段名拼写不一致("score" vs "Score")——round-trip 一测就露馅,
这就是为什么验收标准是"写进去读出来一致"而不是"能生成文件"。</li>
</ul>
</div>
</section>
</section>
<!-- ===================== 下午 1QThread 提升 ===================== -->
<section>
<section>
<p class="module-badge">下午 1 · 提升:多线程防卡界面(新授)</p>
<h2>QThread:耗时操作不冻结界面 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>先看病症:把耗时活儿放主线程 <span class="tag tag-lecture"></span></h3>
<div class="pitfall-box">
<h4>现场演示</h4>
<ul>
<li><code>msleep</code> 循环直接放按钮槽里 → 整个窗口冻结:点不动、拖不动、
系统提示"未响应"——Day4 讲过:<b>主线程阻塞 = 事件循环停摆</b>
这是学员项目里最高频的"卡死"来源。</li>
</ul>
</div>
</section>
<section>
<h3>正解:worker-object 模式 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
auto* thread = new QThread(this);
auto* worker = new ImportWorker; // 不给 parent:有 parent 禁止 moveToThread
worker->moveToThread(thread); // 必须在 start() 之前
connect(thread, &QThread::started, worker, [worker]{ worker->doImport(10); });
connect(worker, &ImportWorker::rowReady, this, /* 更新表格 */); // 自动 Queued
thread->start();
</code></pre>
<div class="concept-box">
<h4>核心概念(三件套)</h4>
<ul>
<li><b>线程亲和性</b>:每个 QObject 归属一个线程,GUI 控件必须归主线程;</li>
<li><b>worker 对象</b>:官方推荐做法——耗时逻辑放 worker,<code>moveToThread</code>
搬进工作线程,而不是继承 QThread 重写 run</li>
<li><b>跨线程信号槽自动 Queued</b>(Day2 的连接方式今天闭环):worker 发信号、
主线程槽更新表格,不需要手动加锁。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>在工作线程直接操作 GUI 控件——未定义行为,跨线程崩溃的头号来源;</li>
<li><code>moveToThread</code> 写在 <code>start()</code> 之后 / worker 带了 parent——都不生效。</li>
</ul>
</div>
<p><small>最小版:<a class="filelink" target="_blank"
href="../examples/qthread_worker/qt_thread_worker.cpp">examples/qthread_worker/</a> ·
案例集成版:<a class="filelink" target="_blank"
href="../examples/student_manager/qt_student_manager.cpp">student_manager 的 startImport()</a></small></p>
</section>
</section>
<!-- ===================== 下午 2:流程与规范 ===================== -->
<section>
<section>
<p class="module-badge">下午 2 · 开发流程与规范 + 分组定方向</p>
<h2>进项目前的最后一课 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>Git 提交习惯 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>每完成一个任务就提交,提交粒度对应任务粒度;commit 信息说清"做了什么、为什么"——
它是项目的可追溯记录,评审和回滚都靠它。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>攒几天代码一次性提交、信息写"update"——历史失去可追溯性,出问题无法定位是哪次
改动引入的。从 Day6 的第一次 commit 开始就按规范来。</li>
</ul>
</div>
</section>
<section>
<h3>考核标准与分组定方向 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>考核不只看代码:项目管理(任务拆解/进度可见)+ 项目技术 + 职业素养(协作/答辩表达)
多维度评估——只埋头写代码会丢掉其他维度的分。</li>
<li>今天散场前每组确认:项目选题 + 初步分工 + 任务拆解草稿(上午"需求拆解"那页
就是模板)。</li>
</ul>
</div>
<div class="verify-box" style="font-size:0.55em;">
<h4>兴趣钩子(各 5 分钟演示,选学)</h4>
<p>给界面加动效:<a class="filelink" target="_blank"
href="../examples/property_animation/qt_property_animation.cpp">QPropertyAnimation</a>(任何
Q_PROPERTY 都能被缓动曲线驱动) ·
多状态 UI<a class="filelink" target="_blank"
href="../examples/state_machine/qt_state_machine.cpp">QStateMachine</a>(状态成为一等对象,
替代一堆 bool 标志)· 线程的更高抽象:QtConcurrent/线程池。</p>
</div>
</section>
</section>
<!-- ===================== 收尾 ===================== -->
<section>
<h2>当日产出验证 · 进入项目期</h2>
<div class="verify-box">
<h4>验收</h4>
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_student_manager
# 自测 1/2:JSON 写入→清空→读回,2 条记录完整
# 自测 2/2:后台线程导入 3 条,总计 5 条,界面未阻塞</code></pre>
<p>每组提交:项目选题 + 分工说明 + 任务拆解草稿。明天 Day6 起:学员编码 + 工程师指导,
Day10 下午项目答辩。</p>
</div>
<p class="footer-note"><small>案例参考实现与讲解建议:
<a class="filelink" target="_blank" href="../examples/student_manager/README.md">examples/student_manager/README.md</a> ·
大纲:<a class="filelink" href="../OUTLINE_4DAY.md">docs/teaching/OUTLINE_4DAY.md</a> §3</small></p>
<p><a class="filelink" href="index.html">返回目录</a> · <a class="filelink" href="day4.html">← 上一天</a></p>
</section>
</div>
</div>
<script src="reveal/reveal.js"></script>
<script src="reveal/plugin/highlight/highlight.js"></script>
<script>
Reveal.initialize({
hash: true, controls: true, progress: true, center: true, slideNumber: true,
plugins: [ RevealHighlight ]
});
</script>
</body>
</html>