新增 4 天教案大纲 + reveal.js 交互幻灯片(docs/teaching/)

大纲按「3天C++强化(含OOP)+1天Qt桌面」组织,讲:练≈1:2.7;每个知识点直接
链接仓库真实代码文件与 ERRATA/VERSION_NOTES 里记录的具体坑。幻灯片
vendor 了 reveal.js 5.2.1(核心+highlight插件),零 CDN 依赖,配 serve.py
本地起服务(仅绑定 127.0.0.1)供离线播放,代码链接可点开查看源码。
This commit is contained in:
张宗平
2026-07-01 16:51:12 +08:00
parent 8e44203929
commit b89819d5db
13 changed files with 2093 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Day 4 · Qt 桌面全景</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">Qt 方向实训 · 第 4 / 4 天 · 讲:练 ≈ 1:2.7</p>
<ul>
<li>模块 A(3 课时):项目结构 / 第一个程序 / 信号槽 / 主窗口</li>
<li>模块 B(3 课时):对话框 / 控件 / 事件 / 绘图 / 文件(速览 + 分组选做)</li>
</ul>
<p><small>定位声明:Day1–3 是「强化已知」,今天是「Qt 零基础入门」——讲授压缩为
骨架级讲解,练习是「跟着例子改」而不是从零写。9 个章节、29 个目标不追求全覆盖。</small></p>
<div class="verify-box" style="font-size:0.5em;">
<h4>开始前准备(一次性)</h4>
<pre><code class="bash" data-trim>tools/install_qt_host.sh
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
cmake --build build</code></pre>
</div>
</section>
<!-- ===================== 模块 A:讲 ===================== -->
<section>
<section>
<p class="module-badge">模块 A · 讲授 35min</p>
<h2>项目结构 / 信号槽 / 主窗口 <span class="tag tag-lecture"></span></h2>
<ul>
<li>最小 Qt 程序结构</li>
<li>控件创建与显示</li>
<li>内置 / 自定义 / lambda 信号槽</li>
<li>QMainWindow 中心部件</li>
</ul>
</section>
<section>
<h3>最小 Qt 程序:QApplication + exec() <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class Widget : public QWidget {
Q_OBJECT // 用到信号槽/属性系统的类都要有这个宏(触发 moc 代码生成)
public:
Widget(QWidget* parent = nullptr) : QWidget(parent) {}
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv); // 整个程序唯一的 QApplication
Widget w;
w.show();
return app.exec(); // 进入事件循环,程序在这里“活着”
}
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a>(注意仓库版本额外注入了
<code>QTimer::singleShot</code> 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)</small></p>
</section>
<section>
<h3>控件创建:两种写法 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
// 第一种:先构造再设属性
QPushButton* btn = new QPushButton;
btn->setParent(this);
btn->setText(QStringLiteral("德玛西亚"));
btn->move(100, 100);
// 第二种:构造时指定文本与父亲
QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></small></p>
</section>
<section>
<h3>信号槽:内置 / 自定义 / 重载消歧义 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class Teacher : public QObject {
Q_OBJECT
signals:
void hungry(); // 无参信号
void hungry(QString food); // 带参信号(重载)
public slots:
void classIsOver(){ emit hungry(); emit hungry(QString("汉堡")); }
};
// 连接重载信号时必须用 QOverload 显式指定版本
connect(t, QOverload&lt;&gt;::of(&Teacher::hungry), s, QOverload&lt;&gt;::of(&Student::treat));
connect(t, QOverload&lt;QString&gt;::of(&Teacher::hungry), s, QOverload&lt;QString&gt;::of(&Student::treat));
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p03/ch04/custom_signal_slot.cpp">p03/ch04/custom_signal_slot.cpp</a>
lambda 槽 + 值捕获/引用捕获对比见
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a></small></p>
</section>
<section>
<h3>QMainWindow:中心部件 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class DemoMainWindow : public QMainWindow {
Q_OBJECT
public:
DemoMainWindow(){
QTextEdit* edit = new QTextEdit(this);
setCentralWidget(edit); // QMainWindow 有固定布局:菜单栏/工具栏/中心部件/停靠窗口/状态栏
}
};
</code></pre>
<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>
<!-- ===================== 模块 A:练 ===================== -->
<section>
<section>
<p class="module-badge">模块 A · 练习 95min</p>
<h2>动手:第一个 Qt 程序 <span class="tag tag-practice"></span></h2>
</section>
<section>
<div class="task-card">
<h4>任务卡 1 · 跑通最小程序(离屏验证) <span class="tag tag-practice"></span></h4>
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app
QT_QPA_PLATFORM=offscreen tools/run_qt.sh button_creation</code></pre>
<p>有 X 环境的同学可去掉 <code>QT_QPA_PLATFORM=offscreen</code> 直接看窗口弹出(200ms 后自动退出)。</p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 2 · 自定义信号槽改文本 <span class="tag tag-practice"></span></h4>
<p>仿 <a class="filelink" target="_blank"
href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a>
给一个按钮连接自定义信号槽:点击后改变一个 <code>QLabel</code> 的文本内容。</p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 3 · 窗口骨架 <span class="tag tag-practice"></span></h4>
<p><code>QMainWindow</code> + <a class="filelink" target="_blank"
href="../../../p03/ch05/dock_widget.cpp">dock_widget.cpp</a> 搭一个「中心区 + 侧边栏」
的窗口骨架。</p>
</div>
</section>
</section>
<!-- ===================== 模块 B:讲 ===================== -->
<section>
<section>
<p class="module-badge">模块 B · 讲授 35min(五个方向各约 7min 速览)</p>
<h2>对话框 / 控件 / 事件 / 绘图 / 文件 <span class="tag tag-lecture"></span></h2>
<p><small>只求「这个方向能做什么」的骨架认知,不逐行讲解</small></p>
</section>
<section>
<h3>对话框:文件对话框 + QTextStream <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
void openFile() {
QString path = QFileDialog::getOpenFileName(this, QStringLiteral("打开文件"));
if (path.isEmpty()) return;
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { /* 提示失败 */ return; }
QTextStream in(&file);
QString text = in.readAll();
QMessageBox::information(this, QStringLiteral("内容"), text);
}
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p03/ch06/file_dialog.cpp">p03/ch06/file_dialog.cpp</a>;模态/非模态对话框见
<a class="filelink" target="_blank" href="../../../p03/ch06/modal_dialog.cpp">modal_dialog.cpp</a></small></p>
</section>
<section>
<h3>常用控件:QLabel 显示图片 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
QPixmap pixmap;
pixmap.load(":/Image/boat.jpg"); // 从 Qt 资源系统(.qrc)加载
QLabel* label = new QLabel(this);
label->setPixmap(pixmap);
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p03/ch08/label_pixmap.cpp">p03/ch08/label_pixmap.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></small></p>
</section>
<section>
<h3>绘图:QPainter <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
void paintEvent(QPaintEvent*) override {
QPainter painter(this);
painter.drawLine(QLine(10, 10, 200, 10));
painter.drawRect(QRect(10, 30, 100, 50));
painter.drawEllipse(QRect(150, 30, 100, 80));
}
</code></pre>
<p><small>所有绘制都发生在 <code>paintEvent</code> 里,由 Qt 在需要重绘时自动调用。
出处:<a class="filelink" target="_blank"
href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.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(); }
QFileInfo info(file);
qDebug() << info.baseName() << info.suffix();
</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>
<!-- ===================== 模块 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>方案 A · 图片查看器雏形 <span class="tag tag-practice"></span></h4>
<p>结合 <a class="filelink" target="_blank"
href="../../../p03/ch08/label_pixmap.cpp">label_pixmap.cpp</a> +
<a class="filelink" target="_blank" href="../../../p03/ch06/file_dialog.cpp">file_dialog.cpp</a>
用文件对话框选一张图片,加载到 <code>QLabel</code> 显示。</p>
</div>
</section>
<section>
<div class="task-card">
<h4>方案 B · 简易绘图板 <span class="tag tag-practice"></span></h4>
<p>结合 <a class="filelink" target="_blank"
href="../../../p03/ch10/qpainter_basic.cpp">qpainter_basic.cpp</a> +
<a class="filelink" target="_blank" href="../../../p03/ch09/mouse_event.cpp">mouse_event.cpp</a>
监听鼠标拖动事件,记录轨迹点,在 <code>paintEvent</code> 里用 <code>QPainter</code> 画出轨迹。</p>
</div>
</section>
<section>
<div class="task-card">
<h4>方案 C · 文本文件小工具 <span class="tag tag-practice"></span></h4>
<p>结合 <a class="filelink" target="_blank"
href="../../../p03/ch11/qfile_basic.cpp">qfile_basic.cpp</a> +
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a>
读一个文本文件,统计行数/字符数,把统计结果写回一个新文件。</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 &lt;target&gt;
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口</code></pre>
<p>每组演示自己选做的方案(A/B/C 三选一),教师验收「编译通过 + 交互符合预期」。</p>
</div>
<p class="footer-note"><small>4 天实训到此结束——大纲:
<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
<p><a class="filelink" href="index.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>