Files
scuc-qt-course/docs/teaching/slides/day4.html
T
张宗平 b89819d5db 新增 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)供离线播放,代码链接可点开查看源码。
2026-07-01 16:51:12 +08:00

307 lines
13 KiB
HTML
Raw 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 · 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>