83620c0cf5
- docs/teaching/OUTLINE.md、slides/day1-5.html、index.html:替换此前自定义的 4 天方案,严格对齐 docs/TRAINING_PLAN_2026.md「C++方向」Day1-5 课堂内容 (Day6-10 为纯项目实作,不在教案范围) - docs/teaching/examples/:官方方案书要求但仓库 p03/(wiki 抓取内容) 未覆盖的 QSS/Model-View/JSON/QThread/QPropertyAnimation/QStateMachine 六个技术点, 各补一个最小可运行示例 + README 教学文档,均已离屏验证跑通;手工维护, 不属于 gen_part3.py 生成产物 - 根 CMakeLists.txt:接入 docs/teaching/examples 构建(BUILD_QT_PART 分支内), 不改动生成器管理的 p01/p03 CMakeLists - 任务卡改为目标/验收标准/基础知识参考三段式模板,custom.css 配套新增样式 - docs/teaching/PRETEST.md:10 题 10 分钟 C++ 摸底测验,验证教案假定的受众 基础是否成立,并与 OUTLINE.md §0 的分层预案挂钩
229 lines
10 KiB
HTML
229 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Day 3 · 高级控件 Model/View / 事件机制 / 文件IO与JSON</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 3</h1>
|
||
<h3>高级控件 Model/View / 事件机制 / 文件 IO 与 JSON</h3>
|
||
<p class="module-badge">C++/Qt 方向实训 · 第 3 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||
<ul>
|
||
<li>模块 A(3 课时):Model/View 架构 / 事件机制</li>
|
||
<li>模块 B(3 课时):文件 IO / JSON 解析 → 本地数据存储案例</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- ===================== 模块 A:讲 ===================== -->
|
||
<section>
|
||
<section>
|
||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||
<h2>Model/View 架构 / 事件机制 <span class="tag tag-lecture">讲</span></h2>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Model/View 模型视图架构(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||
<pre><code class="cpp" data-trim data-noescape>
|
||
// 模型:只管数据
|
||
auto* model = new QStandardItemModel(this);
|
||
model->appendRow(new QStandardItem(QStringLiteral("需求分析")));
|
||
|
||
// 视图:只管展示,不持有数据
|
||
auto* view = new QListView(this);
|
||
view->setModel(model);
|
||
|
||
// 选中状态变化 → 通知外部,模型和视图之间没有直接耦合
|
||
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
|
||
</code></pre>
|
||
<p><small>出处:<a class="filelink" target="_blank"
|
||
href="../examples/model_view/qt_model_view_basics.cpp">docs/teaching/examples/model_view/qt_model_view_basics.cpp</a>
|
||
(<a class="filelink" target="_blank" href="../examples/model_view/README.md">配套 README</a>)——
|
||
仓库 <code>p03/</code> 的 wiki 抓取内容没有 Model/View,这是本轮补写的教学补充示例,已离屏验证跑通。</small></p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>事件循环机制(复用 Day2 概念) <span class="tag tag-lecture">讲</span></h3>
|
||
<p><code>app.exec()</code> 进入的就是事件循环——鼠标点击、键盘输入、定时器
|
||
触发都会变成事件,被塞进这个循环里依次分发。今天讲的"事件过滤器"就是在
|
||
事件被分发给控件之前,插一个"拦截点"。</p>
|
||
<p><small>出处:<a class="filelink" target="_blank"
|
||
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a></small></p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>自定义事件处理 / 事件过滤器 <span class="tag tag-lecture">讲</span></h3>
|
||
<pre><code class="cpp" data-trim data-noescape>
|
||
class EventLabel : public QLabel {
|
||
Q_OBJECT
|
||
public:
|
||
explicit EventLabel(QWidget* p=nullptr):QLabel(p){ setMouseTracking(true); }
|
||
protected:
|
||
void mouseMoveEvent(QMouseEvent* event) override {
|
||
setText(QString("Move: (%1, %2)").arg(event->x()).arg(event->y()));
|
||
}
|
||
};
|
||
</code></pre>
|
||
<p><small>重写 <code>xxxEvent</code> 是 Qt 事件处理的主线之一(另一条线是事件过滤器
|
||
<code>installEventFilter</code>)。出处:<a class="filelink" target="_blank"
|
||
href="../../../p03/ch09/mouse_event.cpp">p03/ch09/mouse_event.cpp</a>;
|
||
事件过滤器见 <a class="filelink" target="_blank" href="../../../p03/ch09/event_filter_mainwindow.cpp">event_filter_mainwindow.cpp</a></small></p>
|
||
</section>
|
||
</section>
|
||
|
||
<!-- ===================== 模块 A:练 ===================== -->
|
||
<section>
|
||
<section>
|
||
<p class="module-badge">模块 A · 练习 95min</p>
|
||
<h2>动手:Model/View + 事件 <span class="tag tag-practice">练</span></h2>
|
||
</section>
|
||
<section>
|
||
<div class="task-card">
|
||
<h4>任务卡 1 · Model/View 换视图 <span class="tag tag-practice">练</span></h4>
|
||
<dl class="task-spec">
|
||
<dt>目标</dt>
|
||
<dd>把 <code>qt_model_view_basics.cpp</code> 里的 <code>QListView</code> 换成
|
||
<code>QTableView</code>,模型代码不改</dd>
|
||
<dt>验收标准</dt>
|
||
<dd>编译通过,界面正常展示同一份数据,证明"同一个模型可以喂给不同视图"</dd>
|
||
<dt>基础知识 · 参考</dt>
|
||
<dd><a class="filelink" target="_blank" href="../examples/model_view/qt_model_view_basics.cpp">qt_model_view_basics.cpp</a> ·
|
||
<a class="filelink" target="_blank" href="../examples/model_view/README.md">README</a></dd>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
<section>
|
||
<div class="task-card">
|
||
<h4>任务卡 2 · 事件过滤器实操 <span class="tag tag-practice">练</span></h4>
|
||
<dl class="task-spec">
|
||
<dt>目标</dt>
|
||
<dd>仿 <code>event_filter_mainwindow.cpp</code>,给一个控件装事件过滤器,拦截鼠标按下事件并打印坐标</dd>
|
||
<dt>验收标准</dt>
|
||
<dd>点击控件后终端/调试输出能看到坐标信息</dd>
|
||
<dt>基础知识 · 参考</dt>
|
||
<dd><a class="filelink" target="_blank" href="../../../p03/ch09/event_filter_mainwindow.cpp">event_filter_mainwindow.cpp</a> ·
|
||
<a class="filelink" target="_blank" href="../../../p03/ch09/mouse_event.cpp">mouse_event.cpp</a></dd>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
<!-- ===================== 模块 B:讲 ===================== -->
|
||
<section>
|
||
<section>
|
||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||
<h2>文件 IO / JSON 解析 <span class="tag tag-lecture">讲</span></h2>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>文件系统:QFile 读文本 <span class="tag tag-lecture">讲</span></h3>
|
||
<pre><code class="cpp" data-trim data-noescape>
|
||
QFile file("in.txt");
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "打开失败"; return -1; }
|
||
while (!file.atEnd()) { qDebug() << file.readLine(); }
|
||
</code></pre>
|
||
<p><small>出处:<a class="filelink" target="_blank"
|
||
href="../../../p03/ch11/qfile_basic.cpp">p03/ch11/qfile_basic.cpp</a>;结构化读写见
|
||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a>、
|
||
<a class="filelink" target="_blank" href="../../../p03/ch11/qdatastream_io.cpp">qdatastream_io.cpp</a></small></p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>JSON 解析与写入(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||
<pre><code class="cpp" data-trim data-noescape>
|
||
QJsonObject student;
|
||
student["name"] = QStringLiteral("张三");
|
||
student["score"] = 92;
|
||
|
||
QJsonDocument doc(student);
|
||
QByteArray bytes = doc.toJson(QJsonDocument::Indented); // 序列化
|
||
|
||
QJsonDocument parsed = QJsonDocument::fromJson(bytes); // 反序列化
|
||
QJsonObject obj = parsed.object();
|
||
qDebug() << obj["name"].toString() << obj["score"].toInt();
|
||
</code></pre>
|
||
<p><small>出处:<a class="filelink" target="_blank"
|
||
href="../examples/json_io/qt_json_parse_write.cpp">docs/teaching/examples/json_io/qt_json_parse_write.cpp</a>
|
||
(<a class="filelink" target="_blank" href="../examples/json_io/README.md">配套 README</a>)——
|
||
仓库 <code>p03/ch11</code> 的 wiki 抓取内容只有 QFile/QTextStream/QDataStream,没有 JSON,
|
||
这是本轮补写的教学补充示例;不需要 GUI,<code>tools/run_qt.sh qt_json_parse_write</code>
|
||
直接跑即可,不需要 <code>QT_QPA_PLATFORM=offscreen</code>。</small></p>
|
||
</section>
|
||
</section>
|
||
|
||
<!-- ===================== 模块 B:练(本地数据存储案例) ===================== -->
|
||
<section>
|
||
<section>
|
||
<p class="module-badge">模块 B · 练习 95min(本地数据存储案例,练习任务,非预建方案)</p>
|
||
<h2>动手:本地数据存储小工具 <span class="tag tag-practice">练</span></h2>
|
||
</section>
|
||
<section>
|
||
<div class="task-card">
|
||
<h4>任务卡 1 · JSON 落盘 <span class="tag tag-practice">练</span></h4>
|
||
<dl class="task-spec">
|
||
<dt>目标</dt>
|
||
<dd>把 <code>qt_json_parse_write.cpp</code> 的 JSON 数据实际写入一个文件(结合
|
||
<code>qfile_basic.cpp</code> 的 <code>QFile</code> 写法),再读回来解析校验</dd>
|
||
<dt>验收标准</dt>
|
||
<dd>程序运行后磁盘上生成一个 <code>.json</code> 文件,内容可读;重新运行程序能正确解析出
|
||
之前写入的数据</dd>
|
||
<dt>基础知识 · 参考</dt>
|
||
<dd><a class="filelink" target="_blank" href="../examples/json_io/qt_json_parse_write.cpp">qt_json_parse_write.cpp</a> ·
|
||
<a class="filelink" target="_blank" href="../examples/json_io/README.md">README</a> ·
|
||
<a class="filelink" target="_blank" href="../../../p03/ch11/qfile_basic.cpp">qfile_basic.cpp</a></dd>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
<section>
|
||
<div class="task-card">
|
||
<h4>任务卡 2 · 本地数据存储小工具 <span class="tag tag-practice">练</span></h4>
|
||
<dl class="task-spec">
|
||
<dt>目标</dt>
|
||
<dd>设计一个"学员信息"结构(姓名/成绩/标签),支持增加一条记录并追加保存到 JSON 文件,
|
||
重启程序后能读出之前保存的全部记录</dd>
|
||
<dt>验收标准</dt>
|
||
<dd>至少能存 2 条以上记录并在重启后完整读回;数据字段不丢失</dd>
|
||
<dt>基础知识 · 参考</dt>
|
||
<dd><a class="filelink" target="_blank" href="../examples/json_io/">docs/teaching/examples/json_io/</a> ·
|
||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a></dd>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
<!-- ===================== 收尾 ===================== -->
|
||
<section>
|
||
<h2>当日产出验证</h2>
|
||
<div class="verify-box">
|
||
<h4>现场演示</h4>
|
||
<p>现场演示本地数据存储小工具,验证"写入 → 重启 → 读回"完整闭环。</p>
|
||
</div>
|
||
<p class="footer-note"><small>明天 Day4:2D 图形绘制 / 多线程 / 动画框架与状态机 ——
|
||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||
<a class="filelink" href="day2.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>
|