幻灯片按 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 失效
This commit is contained in:
+121
-213
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 3 · 高级控件 Model/View / 事件机制 / 文件IO与JSON</title>
|
||||
<title>Day 3 · QMainWindow / QDialog / 布局与控件 / QSS</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">
|
||||
@@ -15,303 +15,211 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 3</h1>
|
||||
<h3>高级控件 Model/View / 事件机制 / 文件 IO 与 JSON</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 3 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>QMainWindow / 对话框 QDialog / 布局管理器 / 常用控件</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 3 / 4 个讲授日</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):Model/View 架构 / 事件机制</li>
|
||||
<li>模块 B(3 课时):文件 IO / JSON 解析 → 本地数据存储案例</li>
|
||||
<li>上午(08:30–11:00 讲 / 11:00–12:00 练):QMainWindow · 对话框 QDialog</li>
|
||||
<li>下午(13:30–16:00 讲 / 16:00–17:00 作业):布局管理器 · 常用控件 · QSS 美化</li>
|
||||
</ul>
|
||||
<p><small>今天的主线:把昨天的计算器升级成"正经软件"——主窗口骨架 + 对话框交互 +
|
||||
布局讲透 + 美化。</small></p>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<!-- ===================== 上午:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>Model/View 架构 / 事件机制 <span class="tag tag-lecture">讲</span></h2>
|
||||
<p class="module-badge">上午 · 讲授 08:30–11:00</p>
|
||||
<h2>QMainWindow / 对话框 QDialog <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Model/View:数据与展示分离 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>QMainWindow:标准桌面软件的骨架 <span class="tag tag-lecture">讲</span></h3>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li>Model/View 把"数据"和"怎么展示数据"彻底拆开:模型(<code>QAbstractItemModel</code>
|
||||
及其子类)只负责存数据、回答"这个位置是什么",视图(<code>QListView</code>/
|
||||
<code>QTableView</code>/...)只负责画——同一份模型可以同时喂给多个不同视图。</li>
|
||||
<li>模型用统一的 <code>QModelIndex</code>(行/列/父项)寻址数据,视图向模型要数据时
|
||||
还要带上"角色"(<code>Qt::DisplayRole</code> 显示文本、<code>Qt::DecorationRole</code>
|
||||
图标……),同一个位置可以按不同角色返回不同内容。</li>
|
||||
<li>QMainWindow 不是"大号 QWidget":它有固定的区域布局协议——菜单栏 / 工具栏 /
|
||||
状态栏 / 停靠窗(dock)各就其位,<b>中心部件必须 <code>setCentralWidget()</code>
|
||||
显式安放</b>;菜单项和工具栏按钮的统一抽象是 <code>QAction</code>(一个动作,
|
||||
多处复用)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>直接改模型内部数据却不发信号(<code>dataChanged</code>/<code>beginInsertRows</code>+
|
||||
<code>endInsertRows</code>),视图不会自动刷新,界面和数据"对不上"。</li>
|
||||
<li>用了简单的 <code>QStandardItemModel</code> 却又想自定义复杂数据结构:应该继承
|
||||
<code>QAbstractItemModel</code> 自己实现,而不是硬塞进 Item 模型。</li>
|
||||
<li>直接往 QMainWindow 上摆控件而不设中心部件——布局行为不可预期;</li>
|
||||
<li><code>QAction</code> 显示出来 ≠ 生效:忘了 <code>connect</code> 到槽,
|
||||
就是"菜单点了没反应"。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch05/central_widget.cpp">p03/ch05/central_widget.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch05/dock_widget.cpp">dock_widget.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Model/View 模型视图架构(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>QDialog:模态与非模态 <span class="tag tag-lecture">讲</span></h3>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li><b>模态</b> <code>exec()</code>:阻塞等用户给结果,返回值就是用户的选择
|
||||
(Accepted/Rejected);<b>非模态</b> <code>show()</code>:立即返回,窗口独立存活
|
||||
——所以对话框对象必须活过当前函数作用域(堆 + parent,或
|
||||
<code>WA_DeleteOnClose</code> 自清理)。</li>
|
||||
<li>标准对话框拿来就用:<code>QMessageBox</code>(提示/确认)、<code>QFileDialog</code>(选文件)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑(昨天的所有权坑第三次出现!)</h4>
|
||||
<ul>
|
||||
<li><b>非模态对话框建在栈上</b>:<code>show()</code> 完函数一返回就析构——窗口一闪而过。
|
||||
<code>p03/ch06</code> 的三个 modeless 版本(栈/堆/DeleteOnClose)就是这个坑的对照教材。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch06/modal_dialog.cpp">p03/ch06/modal_dialog.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/modeless_dialog_stack.cpp">modeless_dialog_stack.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/modeless_dialog_heap.cpp">modeless_dialog_heap.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/modeless_dialog_deleteonclose.cpp">modeless_dialog_deleteonclose.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>标准对话框速览 <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 ret = QMessageBox::question(this, "确认", "真的要删除吗?");
|
||||
if (ret == QMessageBox::Yes) { /* ... */ }
|
||||
|
||||
// 视图:只管展示,不持有数据
|
||||
auto* view = new QListView(this);
|
||||
view->setModel(model);
|
||||
|
||||
// 选中状态变化 → 通知外部,模型和视图之间没有直接耦合
|
||||
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
|
||||
QString path = QFileDialog::getOpenFileName(this, "选择文件", "", "*.json");
|
||||
if (!path.isEmpty()) { /* ... */ } // 用户可能点取消:必须处理空返回
|
||||
</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>事件循环:事件 vs 信号 <span class="tag tag-lecture">讲</span></h3>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li><code>app.exec()</code> 进入的就是事件循环(复用
|
||||
<a class="filelink" target="_blank" href="day2.html">Day2</a> 概念)——鼠标点击、
|
||||
键盘输入、定时器触发都会变成"事件"(<code>QEvent</code>),被塞进这个循环里依次分发给
|
||||
对应控件。</li>
|
||||
<li>事件和信号是两条不同的机制:事件是"操作系统/框架推给控件"的通知,走
|
||||
<code>event()</code>/<code>xxxEvent()</code> 虚函数或事件过滤器;信号槽是"对象之间"
|
||||
主动通知,走 <code>connect()</code>。控件收到事件后,常见做法就是在事件处理函数里
|
||||
<code>emit</code> 一个信号,把"底层事件"转成"业务语义通知"。</li>
|
||||
<li>今天讲的"事件过滤器"就是在事件被分发给控件之前,插一个"拦截点"。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>在事件循环所在的主线程做耗时操作会直接卡住整个界面(无法重绘、按钮点了没反应)——
|
||||
这也是 <a class="filelink" target="_blank" href="day4.html">Day4</a> 要讲 QThread 的原因。</li>
|
||||
<li>不处理"用户点了取消"(空路径/Rejected)就直接往下用——健壮性问题的高发点。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</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>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li>拦截事件有两条路:重写 <code>xxxEvent()</code>(只影响这一个控件自己)或装事件过滤器
|
||||
<code>installEventFilter()</code>(可以在事件到达目标控件之前,由另一个对象统一拦截,
|
||||
常用于"一个地方管多个控件"的场景)。</li>
|
||||
<li>事件对象有 <code>accept()</code>/<code>ignore()</code> 状态:处理完事件要表明"我
|
||||
吃掉了这个事件"还是"继续往上传",决定事件是否会继续传播给父控件。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>重写 <code>xxxEvent()</code> 时忘记调用基类实现(如 <code>QWidget::mouseMoveEvent</code>),
|
||||
可能丢失控件本身需要的默认行为。</li>
|
||||
<li>事件过滤器安装到一个对象上后,如果被观察对象先于过滤器对象销毁(或反过来),要注意
|
||||
生命周期——Qt 会在对象销毁时自动清理,但过滤逻辑里持有的裸指针不会自动清空。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</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>
|
||||
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch06/message_box_question.cpp">p03/ch06/message_box_question.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/message_box_custom.cpp">message_box_custom.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/file_dialog.cpp">file_dialog.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>
|
||||
<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 · Model/View 换视图 <span class="tag tag-practice">练</span></h4>
|
||||
<h4>任务卡 1 · 给计算器套上 QMainWindow <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>
|
||||
<dd>把昨天的计算器改造为 QMainWindow 应用:加菜单「帮助→关于」弹
|
||||
<code>QMessageBox</code>;加一个「历史」停靠窗(可先放空 QLabel)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>编译通过,界面正常展示同一份数据,证明"同一个模型可以喂给不同视图"</dd>
|
||||
<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>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch05/central_widget.cpp">p03/ch05/central_widget.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch05/dock_widget.cpp">dock_widget.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/message_box_question.cpp">p03/ch06/message_box_question.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>
|
||||
<p class="module-badge">下午 · 讲授 13:30–16:00</p>
|
||||
<h2>布局管理器 / 常用控件 / QSS 美化 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>QIODevice:统一的读写抽象 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>布局管理器(回收昨天的脚手架) <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> 这一统一读写接口上——同样的"打开/读/写/关闭"套路,以后遇到网络
|
||||
套接字/串口一类设备也是同一套接口,学一次可以迁移到别的设备类型。</li>
|
||||
<li><code>QTextStream</code> 面向"文本行"(人可读),<code>QDataStream</code> 面向
|
||||
"结构化二进制"(保留类型信息,程序间高效互传但人不可直接读)——按需求选,不是谁更好。</li>
|
||||
<li>布局管理器接管子控件的位置/大小计算,随窗口缩放自动重排——昨天计算器里"先用后学"
|
||||
的 <code>QGridLayout</code> 今天讲透:QHBox/QVBox/QGrid 三兄弟 + 嵌套组合 + 伸缩因子
|
||||
<code>addStretch()</code>;布局对象同样遵循 QObject 父子所有权。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>忘记检查 <code>open()</code> 的返回值:文件不存在/无权限时静默失败,后续读写在一个
|
||||
没打开的设备上操作,容易读出空数据而不报错。</li>
|
||||
<li>混淆文本模式和二进制模式(<code>QIODevice::Text</code>):文本模式下 Qt 会做换行符
|
||||
转换,用来读写二进制数据会破坏内容。</li>
|
||||
<li>同一控件不能属于两个布局;</li>
|
||||
<li>布局设置后又用 <code>setGeometry()</code>/<code>move()</code> 手动改位置——
|
||||
会被下次布局刷新覆盖,看起来"设置没生效"。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch08/custom_widget.cpp">p03/ch08/custom_widget.cpp</a></small></p>
|
||||
</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>
|
||||
<h3>常用控件 + 资源系统 .qrc <span class="tag tag-lecture">讲</span></h3>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li>JSON 读写的本质是"内存对象 ↔ 文本"的一对互逆操作:序列化(<code>QJsonDocument::toJson</code>)
|
||||
把内存数据变成可存/可传的文本,反序列化(<code>fromJson</code>)再变回来——正确性的标准是
|
||||
"写出去再读回来,数据要一致"(round-trip)。</li>
|
||||
<li><code>QJsonValue</code> 是一个变体(variant)类型,能装字符串/数字/布尔/数组/对象,
|
||||
取值时要按预期类型调用 <code>toString()</code>/<code>toInt()</code> 等。</li>
|
||||
<li>QLabel 一个控件三种形态:纯文本 / 富文本 HTML / 图片(<code>QPixmap</code>)/
|
||||
动图(<code>QMovie</code>);输入靠 QLineEdit,按钮族 QPushButton/QRadioButton/QCheckBox。</li>
|
||||
<li><code>:/...</code> 路径是 Qt 资源系统:资源编译期打包进可执行文件,运行时不依赖
|
||||
外部文件——发布不用带一堆散图。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>忘记检查 <code>QJsonDocument::fromJson</code> 的 <code>QJsonParseError</code>:
|
||||
文本格式错误时返回一个空文档,后续取值全是默认值,不会崩溃也不会报错,很难定位。</li>
|
||||
<li>类型不匹配时 <code>toInt()</code>/<code>toString()</code> 等不会抛异常,只会静默
|
||||
返回默认值(0/空串),排查时容易误以为"数据没写进去"。</li>
|
||||
<li><code>.qrc</code> 里的路径与代码引用不一致:<b>运行时静默失败</b>(图不显示),
|
||||
不报编译错误;忘接 CMake 的 AUTORCC/<code>qt5_add_resources</code> 资源系统不生效。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p><small>出处:<a class="filelink" target="_blank" href="../../../p03/ch08/label_text_html.cpp">p03/ch08/label_text_html.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch08/label_pixmap.cpp">label_pixmap.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch08/label_movie.cpp">label_movie.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>
|
||||
<h3>QSS 界面美化 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="css" data-trim>QPushButton#okButton { background-color: #2f855a; border-radius: 6px; }
|
||||
QPushButton#okButton:hover { background-color: #276749; }</code></pre>
|
||||
<div class="concept-box">
|
||||
<h4>核心概念</h4>
|
||||
<ul>
|
||||
<li>语法脱胎于 CSS:类型选择器 / ID 选择器(<code>#objectName</code>)/ 伪状态
|
||||
(<code>:hover</code> <code>:pressed</code>)+ 层叠继承——作用对象是 QWidget 树而非 DOM;
|
||||
"长什么样"与"做什么"分离,改样式不碰逻辑。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pitfall-box">
|
||||
<h4>常见的坑</h4>
|
||||
<ul>
|
||||
<li>ID 选择器必须先 <code>setObjectName()</code>;</li>
|
||||
<li><code>setStyleSheet()</code> 会级联影响<b>所有子控件</b>——范围失控是最容易踩的坑,
|
||||
样式尽量写窄(带选择器),不要裸写属性。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<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>
|
||||
href="../examples/qss_styling/qt_qss_styling.cpp">examples/qss_styling/qt_qss_styling.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">README</a>)</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练(本地数据存储案例) ===================== -->
|
||||
<!-- ===================== 下午:作业 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min(本地数据存储案例,练习任务,非预建方案)</p>
|
||||
<h2>动手:本地数据存储小工具 <span class="tag tag-practice">练</span></h2>
|
||||
<p class="module-badge">下午 · 课堂作业 16:00–17:00(工程师答疑)</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>
|
||||
<h4>任务卡 2 · 用 QSS 美化计算器 <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>
|
||||
<dd>数字键/运算键/等号键用 QSS 区分颜色;整体布局改为随窗口缩放自适应</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>程序运行后磁盘上生成一个 <code>.json</code> 文件,内容可读;重新运行程序能正确解析出
|
||||
之前写入的数据</dd>
|
||||
<dd>至少 2 类按钮有不同 <code>background-color</code>;至少 1 个按钮有
|
||||
<code>:hover</code> 或 <code>:pressed</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>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/qss_styling/qt_qss_styling.cpp">examples/qss_styling/</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch08/custom_widget.cpp">p03/ch08/custom_widget.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
@@ -321,13 +229,13 @@ qDebug() << obj["name"].toString() << obj["score"].toInt();
|
||||
<section>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>现场演示</h4>
|
||||
<p>现场演示本地数据存储小工具,验证"写入 → 重启 → 读回"完整闭环。</p>
|
||||
<h4>验收</h4>
|
||||
<p>现场演示升级后的计算器:主窗口(菜单/停靠窗)+ QSS 配色 + 缩放自适应,功能不回退。</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> ·
|
||||
<p class="footer-note"><small>钩子 · QSS 与前端 CSS 是同源思想;Qt Designer/QML 是另一条 UI
|
||||
路线(拖拽/声明式),本课程走纯代码路线,知道它们存在即可。<br>
|
||||
明天 Day4:事件机制 / 绘图 / 表格 + 文件系统——数据要开始落盘了。</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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user