0f81edcc74
- 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 失效
231 lines
10 KiB
HTML
231 lines
10 KiB
HTML
<!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>回指 Day3–4</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>
|
||
|
||
<!-- ===================== 下午 1:QThread 提升 ===================== -->
|
||
<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>
|