Files
张宗平 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

293 lines
14 KiB
HTML
Raw Permalink 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 4 · 事件机制 / 绘图与表格 / 文件系统</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 4</h1>
<h3>Qt 消息机制和事件 / 绘图事件及表格事件 / 文件系统</h3>
<p class="module-badge">C++/Qt 方向实训 · 第 4 / 4 个讲授日</p>
<ul>
<li>上午(08:3011:00 讲 / 11:0012:00 练):事件循环与派发 · 事件拦截 · 绘图事件 · 表格</li>
<li>下午(13:3016:00 讲 / 16:0017:00 作业):文件系统(QFile/QTextStream/QDataStream + JSON</li>
</ul>
<p><small>今晚的作业「本地数据存储小工具」是明天 Day5 综合案例的直接前身。</small></p>
</section>
<!-- ===================== 上午:讲 ===================== -->
<section>
<section>
<p class="module-badge">上午 · 讲授 08:3011:00</p>
<h2>事件机制 / 绘图事件 / 表格事件 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>事件循环与派发:事件 vs 信号 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Day2 的 <code>app.exec()</code> 今天拆开看:事件循环不停从队列取 <code>QEvent</code>
派发给目标控件——<b>事件</b>是框架推给控件的通知(鼠标/键盘/重绘),<b>信号槽</b>
对象间的主动通知,两套机制并存不冲突。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑(明天的伏笔)</h4>
<ul>
<li><b>主线程做耗时操作 = 事件循环停摆 = 整个界面冻结</b>——点不动、拖不动、白屏。
这是学员项目里最高频的"卡死"来源,正确解法(多线程)明天综合案例专门讲。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a><code>exec()</code></small></p>
</section>
<section>
<h3>事件拦截:重写 xxxEvent vs 事件过滤器 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>两条拦截路径:重写 <code>mousePressEvent()</code> 等虚函数(只影响自己);
<code>installEventFilter()</code>(一个对象统一拦截多个控件的事件)。
事件有 <code>accept()</code>/<code>ignore()</code> 状态,决定是否继续向父控件传播。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>重写事件函数忘记调用基类实现(<code>QWidget::mousePressEvent(e)</code>)——
控件默认行为悄悄丢失,现象诡异难查。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch09/event_override_customwidget.cpp">p03/ch09/event_override_customwidget.cpp</a> ·
<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></small></p>
</section>
<section>
<h3>绘图事件:QPainter <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Qt 绘制是<b>框架驱动</b>而非"想画就画":所有绘制只能发生在 <code>paintEvent()</code>
里,由 Qt 按需调用;数据变了想重画,调 <code>update()</code>(异步排队一次重绘请求)。
绘图设备三件套 QImage/QPixmap/QPicture 作参考。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li><code>paintEvent</code> 之外直接构造 QPainter 画窗口——未定义行为;</li>
<li>数据变了忘调 <code>update()</code>——界面不会自动重画。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.cpp</a>
(设备类参考:<a class="filelink" target="_blank" href="../../../p03/ch10/paint_device_qimage.cpp">paint_device_qimage.cpp</a> 等)</small></p>
</section>
<section>
<h3>表格:QTableWidget 与单元格事件 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>item 类控件:数据直接塞进控件(<code>QTableWidgetItem</code>new 出来交给表格
接管所有权),上手快、中小项目够用;"表格事件"以信号暴露:
<code>cellClicked</code> / <code>cellDoubleClicked</code> / <code>itemChanged</code></li>
</ul>
</div>
<div class="concept-box">
<h4>钩子 · 它背后是完整的 Model/View 架构</h4>
<ul>
<li>数据(model)与展示(view)彻底分离、同一模型可喂多个视图(QListView/QTableView);
改模型必须发 <code>dataChanged</code> 等信号视图才会刷新。数据规模大/多视图联动时的
正解,自学入口:<a class="filelink" target="_blank"
href="../examples/model_view/qt_model_view_basics.cpp">examples/model_view/</a></li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank"
href="../examples/table_widget/qt_table_widget_basics.cpp">examples/table_widget/qt_table_widget_basics.cpp</a>
<a class="filelink" target="_blank" href="../examples/table_widget/README.md">README</a></small></p>
</section>
</section>
<!-- ===================== 上午:练 ===================== -->
<section>
<section>
<p class="module-badge">上午 · 课堂练习 11:00–12:00(工程师指导、答疑)</p>
<h2>动手:拦事件 + 画图形 <span class="tag tag-practice"></span></h2>
</section>
<section>
<div class="task-card">
<h4>任务卡 1 · 事件过滤器 + 自绘图形 <span class="tag tag-practice"></span></h4>
<dl class="task-spec">
<dt>目标</dt>
<dd>① 给一个控件装事件过滤器,拦截鼠标按下并打印坐标;
② 在 <code>paintEvent</code> 里画一个组合图形(棋盘格或仪表盘刻度)</dd>
<dt>验收标准</dt>
<dd>点击控件终端能看到坐标输出;图形正确绘制(可截图验证)</dd>
<dt>基础知识 · 参考</dt>
<dd><a class="filelink" target="_blank" href="../../../p03/ch09/event_filter_mainwindow.cpp">p03/ch09/event_filter_mainwindow.cpp</a> ·
<a class="filelink" target="_blank" href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.cpp</a></dd>
</dl>
</div>
</section>
</section>
<!-- ===================== 下午:讲 ===================== -->
<section>
<section>
<p class="module-badge">下午 · 讲授 13:3016:00</p>
<h2>文件系统:QFile / 流 / JSON <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>QIODevice 统一读写抽象 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li><code>QFile</code>/<code>QTextStream</code>/<code>QDataStream</code> 都建立在
<code>QIODevice</code> 统一接口上——之后遇到网络/串口也是同一套;
<code>QTextStream</code> 面向文本行,<code>QDataStream</code> 面向结构化二进制。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li><code>open()</code> 返回值不检查——文件不存在/没权限时<b>静默失败</b>
后续读写全是空操作;</li>
<li>文本/二进制模式混淆会破坏内容。</li>
</ul>
</div>
<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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>序列化/反序列化是一对互逆操作,正确性标准是 <b>round-trip</b>:写出去再读回来
数据一致;<code>QJsonDocument/Object/Array/Value</code> 四件套,
<code>QJsonValue</code> 是能装多种类型的变体。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>不检查 <code>QJsonParseError</code>:格式坏的输入<b>静默变成空文档</b></li>
<li>类型不匹配时 <code>toInt()</code>/<code>toString()</code> 不报错只返回默认值——
数据悄悄丢,很难查。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank"
href="../examples/json_io/qt_json_parse_write.cpp">examples/json_io/qt_json_parse_write.cpp</a>
<a class="filelink" target="_blank" href="../examples/json_io/README.md">README</a></small></p>
</section>
<section>
<h3>★ 相对路径与工作目录 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
QFile bad("data.json"); // ✗ 相对当前工作目录——它在哪取决于你怎么启动
QFile good(QCoreApplication::applicationDirPath() + "/data.json"); // ✓ 锚定可执行文件位置
</code></pre>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>相对路径基于<b>进程当前工作目录</b>——不是源码目录,也不一定是可执行文件目录;
IDE、终端、脚本三种启动方式的工作目录可能各不相同。正解:
<code>applicationDirPath()</code> 拼路径,或只读资源走 <code>.qrc</code>(昨天的资源卡)。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>"IDE 里能读到文件,命令行跑就说不存在"——几乎每届学员必问,根源就是这页。</li>
</ul>
</div>
</section>
<section>
<h3>★ 文本编码再遇(文件版) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
QTextStream ts(&file);
ts.setCodec("UTF-8"); // Qt 5 默认编码随平台/区域设置而变——跨机器必须显式指定
</code></pre>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Day2 界面乱码(QString/源码编码)和今天文件乱码是<b>同一根源</b>的两次出现:
字节 ↔ 文字之间永远隔着一层"编码",跨边界(源码/文件/网络)就必须显式指定。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>Windows 记事本写的文件 Linux 程序读出乱码(或反过来)——不是"文件坏了",
是两边默认编码不同;养成条件反射:<b>编码要显式指定</b></li>
</ul>
</div>
</section>
</section>
<!-- ===================== 下午:作业 ===================== -->
<section>
<section>
<p class="module-badge">下午 · 课堂作业 16:00–17:00(工程师答疑)</p>
<h2>作业:本地数据存储小工具 <span class="tag tag-practice"></span></h2>
</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 条以上记录并在重启后完整读回;字段不丢失;数据文件路径用
<code>applicationDirPath()</code> 拼(今天的坑卡现学现用)</dd>
<dt>基础知识 · 参考</dt>
<dd><a class="filelink" target="_blank" href="../examples/json_io/qt_json_parse_write.cpp">examples/json_io/</a> ·
<a class="filelink" target="_blank" href="../../../p03/ch11/qfile_basic.cpp">p03/ch11/qfile_basic.cpp</a></dd>
</dl>
</div>
</section>
</section>
<!-- ===================== 收尾 ===================== -->
<section>
<h2>当日产出验证</h2>
<div class="verify-box">
<h4>验收</h4>
<p>现场演示「写入 → 重启 → 读回」完整闭环;讲得出 open()/ParseError 两处检查为什么不能省。</p>
</div>
<p class="footer-note"><small>明天 Day5 全天综合项目案例:学员信息管理器——今天的作业就是
它的雏形;还要补上最后一块拼图:多线程防卡界面。</small></p>
<p><a class="filelink" href="index.html">返回目录</a> · <a class="filelink" href="day3.html">← 上一天</a> ·
<a class="filelink" href="day5.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>