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 失效
293 lines
14 KiB
HTML
293 lines
14 KiB
HTML
<!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:30–11:00 讲 / 11:00–12:00 练):事件循环与派发 · 事件拦截 · 绘图事件 · 表格</li>
|
||
<li>下午(13:30–16:00 讲 / 16:00–17:00 作业):文件系统(QFile/QTextStream/QDataStream + JSON)</li>
|
||
</ul>
|
||
<p><small>今晚的作业「本地数据存储小工具」是明天 Day5 综合案例的直接前身。</small></p>
|
||
</section>
|
||
|
||
<!-- ===================== 上午:讲 ===================== -->
|
||
<section>
|
||
<section>
|
||
<p class="module-badge">上午 · 讲授 08:30–11: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:30–16: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>
|