ea6f12e7ac
- tools/teaching_ppt/gen.py:python-pptx 生成引擎(页面内容与讲稿数据分离, 幂等可重生成);tools/teaching_ppt/day1..5.py:每天的页面+讲稿数据 - docs/teaching/ppt/day1..5.pptx:5 份 PPT 产物,页面内容对应 slides/*.html, 详细讲稿(建议时长/讲法/演示/互动/过渡)只写入演讲者备注,不进主页面 - 讲稿去 AI 味:破折号过度堆砌 152 处砍到 6 处(7.8‰ → 0.3‰)、 升华腔(分水岭/点燃探索欲)清零、重复词(现场 26→11)替换; 保留教学口语生动感(你们/当堂/点破/埋点/回指)与【过渡】悬念钩子 - 生成方式:python-pptx 在隔离 venv 装,不影响系统 Python; 改 day*.py 的 notes 字段后重跑 gen.py 即可幂等重生成
191 lines
14 KiB
Python
191 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Day3 · QMainWindow / QDialog / 布局与控件 / QSS —— 页面对应 slides/day3.html。"""
|
||
|
||
SLIDES = [
|
||
{
|
||
'kind': 'title',
|
||
'badge': 'C++/Qt 方向实训 · 第 3 / 4 个讲授日',
|
||
'title': 'Day 3 · QMainWindow / QDialog / 布局与控件',
|
||
'subtitle': '主窗口骨架 / 对话框交互 / 布局讲透 / 常用控件 / QSS 美化',
|
||
'bullets': [
|
||
'上午 08:30–11:00 讲授:QMainWindow · 对话框 QDialog · 标准对话框',
|
||
'上午 11:00–12:00 课堂练习:计算器套上 QMainWindow(菜单 + 停靠窗)',
|
||
'下午 13:30–16:00 讲授:布局管理器(回收脚手架)· 常用控件与 .qrc · QSS',
|
||
'下午 16:00–17:00 作业:QSS 美化计算器 + 缩放自适应',
|
||
],
|
||
'minutes': 3, 'segment': '开场',
|
||
'notes': """【主线】今天全天围绕一件事:把昨天的计算器升级成"正经软件"。上午给骨架(主窗口加对话框),下午管排版和外观。开场就展示教师版的"升级后计算器"成品,让学员看到今天结束时自己的程序长什么样,目标可视化。
|
||
【时长骨架】上午:QMainWindow 30 → QDialog 模态非模态 30 → 标准对话框 15,讲授约 75 分钟,剩余给随堂跟敲和答疑。下午:布局 30 → 控件与 qrc 25 → QSS 25,随堂练穿插。
|
||
【衔接】昨天的"堆上建控件"军规今天在对话框一节会第三次出现,留意把这条线讲成呼应而不是重复。"""
|
||
},
|
||
{
|
||
'badge': '上午 · 讲授 08:30–11:00',
|
||
'title': 'QMainWindow:标准桌面软件的骨架',
|
||
'boxes': [
|
||
('concept', None, [
|
||
'QMainWindow 不是「大号 QWidget」:菜单栏/工具栏/状态栏/停靠窗各就其位的固定区域协议',
|
||
'中心部件必须 setCentralWidget() 显式安放',
|
||
'菜单项和工具栏按钮的统一抽象是 QAction——一个动作,多处复用',
|
||
]),
|
||
('pitfall', None, [
|
||
'不设中心部件直接摆控件——布局行为不可预期',
|
||
'QAction 显示出来 ≠ 生效:忘 connect 就是「菜单点了没反应」',
|
||
]),
|
||
],
|
||
'source': '出处:p03/ch05/central_widget.cpp · dock_widget.cpp',
|
||
'minutes': 30, 'segment': '上午 · QMainWindow',
|
||
'notes': """【引入】投一张学员都认识的软件截图(VS Code / WPS),指认五个区域:菜单/工具/状态/中心/停靠。"QMainWindow 就是这套行业标准布局的现成实现。"
|
||
【讲法】跑 central_widget.cpp 与 dock_widget.cpp,边跑边对照代码:setCentralWidget、menuBar()->addMenu、addToolBar、statusBar()->showMessage 四个入口各演示一次。
|
||
【QAction 重点】用"一个动作三个入口"讲:同一个 QAction 同时挂菜单和工具栏,禁用它两处一起变灰,这就是抽象的价值。坑卡里"菜单点了没反应"发生率极高,让学员现在就形成"建 action 后第一件事 connect"的顺序习惯。
|
||
【互动】问:"中心部件只能有一个,那复杂界面怎么办?"答案是中心部件本身可以是任意组合控件(下午布局课解决),先埋问题。
|
||
【过渡】"主窗口是常驻界面;临时冒出来问一句的界面叫对话框。" """
|
||
},
|
||
{
|
||
'badge': '上午 · 讲授 08:30–11:00',
|
||
'title': 'QDialog:模态与非模态',
|
||
'boxes': [
|
||
('concept', None, [
|
||
'模态 exec():阻塞等结果,返回值就是用户的选择(Accepted/Rejected)',
|
||
'非模态 show():立即返回,窗口独立存活——对象必须活过当前函数作用域',
|
||
'非模态三种写法:堆 + parent / WA_DeleteOnClose 自清理 /(反例)栈',
|
||
]),
|
||
('pitfall', '常见的坑(所有权军规第三次出现!)', [
|
||
'非模态对话框建在栈上:show() 完函数一返回就析构——窗口一闪而过',
|
||
'p03/ch06 的三个 modeless 版本就是这个坑的对照教材',
|
||
]),
|
||
],
|
||
'source': '出处:p03/ch06/modal_dialog.cpp · modeless_dialog_stack.cpp · _heap.cpp · _deleteonclose.cpp',
|
||
'minutes': 30, 'segment': '上午 · QDialog',
|
||
'notes': """【讲法】模态/非模态先用生活类比定性:模态=收银台付款(不办完不能干别的),非模态=旁边开着的计算器(随时切换)。然后讲代码差异的本质:exec() 内部自己转事件循环所以能阻塞,show() 只是显示。
|
||
【三连跑对照】依次跑 ch06 三个 modeless 文件:栈版一闪而过(学员先猜原因,Day2 军规该有人能答上)、堆版正常但要考虑谁删、DeleteOnClose 版关窗自清理。同一功能三种生命周期策略并排,是全仓库最好的所有权教材。
|
||
【收束】黑板军规更新:"给了 parent 的控件必须 new"加"非模态对话框必须活过函数"。本质是同一条:对象的生命周期必须覆盖它的使用周期。
|
||
【过渡】"自己写对话框之前,先看看 Qt 送了哪些现成的。" """
|
||
},
|
||
{
|
||
'badge': '上午 · 讲授 08:30–11:00',
|
||
'title': '标准对话框速览',
|
||
'code': 'auto ret = QMessageBox::question(this, "确认", "真的要删除吗?");\nif (ret == QMessageBox::Yes) { /* ... */ }\n\nQString path = QFileDialog::getOpenFileName(this, "选择文件", "", "*.json");\nif (!path.isEmpty()) { /* ... */ } // 用户可能点取消:必须处理空返回',
|
||
'boxes': [
|
||
('pitfall', None, [
|
||
'不处理「用户点了取消」(空路径/Rejected)就直接往下用——健壮性问题高发点',
|
||
]),
|
||
],
|
||
'source': '出处:p03/ch06/message_box_question.cpp · message_box_custom.cpp · file_dialog.cpp',
|
||
'minutes': 15, 'segment': '上午 · 标准对话框',
|
||
'notes': """【讲法】两个静态函数各演示一次即可,重点全放在坑卡上:让学员齐声回答"getOpenFileName 之后第一行代码写什么?"判空。用户永远可能点取消,这是健壮性思维的第一课(Day4 的 open() 判返回值、ParseError 检查是同一条思维线)。
|
||
【提示】QMessageBox 的 question/information/warning/critical 四兄弟提一嘴图标差异即可,不逐个演示。
|
||
【过渡】"骨架和对话框都有了。上午最后一小时,把你的计算器装进这副骨架。" """
|
||
},
|
||
{
|
||
'badge': '上午 · 课堂练习 11:00–12:00(工程师指导、答疑)',
|
||
'title': '任务卡 1 · 给计算器套上 QMainWindow',
|
||
'boxes': [
|
||
('task', '任务卡 1', [
|
||
'目标:昨天的计算器改造为 QMainWindow 应用;加菜单「帮助→关于」弹 QMessageBox;加「历史」停靠窗(可先放空 QLabel)',
|
||
'验收:计算器功能不回退;菜单动作有响应;停靠窗可拖动/收起',
|
||
'参考:p03/ch05/central_widget.cpp · dock_widget.cpp · p03/ch06/message_box_question.cpp',
|
||
]),
|
||
],
|
||
'minutes': 60, 'segment': '上午 · 课堂练习',
|
||
'notes': """【关键改造思路(先讲 3 分钟再放手)】昨天的计算器是一个 QWidget,今天不重写,把它整个 setCentralWidget 进 QMainWindow。这个"旧组件装进新骨架"的动作本身就是组件化思维的第一次实践,点破它。
|
||
【巡场检查点】①菜单 QAction 忘 connect(上午坑卡现场版,让学员自己对照坑卡定位);②功能回退(改造弄坏了昨天的计算逻辑,借机讲"改结构不改逻辑"的重构纪律)。
|
||
【分层】快的学员让"历史"停靠窗真的记录每次运算(QLabel 追加文本即可),这直接为 Day5 案例的表格记录做铺垫。
|
||
【收尾】抽 1–2 人演示:点菜单弹关于、拖动停靠窗。"""
|
||
},
|
||
{
|
||
'badge': '下午 · 讲授 13:30–16:00',
|
||
'title': '布局管理器(回收昨天的脚手架)',
|
||
'boxes': [
|
||
('concept', None, [
|
||
'布局接管子控件位置/大小计算,随窗口缩放自动重排',
|
||
'QHBox / QVBox / QGrid 三兄弟 + 嵌套组合 + 伸缩因子 addStretch()',
|
||
'布局对象同样遵循 QObject 父子所有权',
|
||
]),
|
||
('pitfall', None, [
|
||
'同一控件不能属于两个布局',
|
||
'布局设置后又用 setGeometry()/move() 手动改位置——被下次刷新覆盖,看起来「设置没生效」',
|
||
]),
|
||
],
|
||
'source': '出处:p03/ch08/custom_widget.cpp',
|
||
'minutes': 30, 'segment': '下午 · 布局',
|
||
'notes': """【兑现承诺】开场明确"昨天欠的布局课现在还":把计算器脚手架里的 QGridLayout 拿出来讲原理。为什么 addWidget(w, row, col) 之后拉伸窗口按钮会跟着变?因为布局在每次 resize 时重算所有子控件的几何。
|
||
【讲法】演示三兄弟各一个最小例子,然后重点讲嵌套:垂直布局套水平布局,拼出"上显示屏下键盘"的计算器结构。学员昨天已经无意识用过,今天补上心智模型。addStretch 用"弹簧"类比。
|
||
【坑卡】当堂演示"布局后 move() 失效":move 完拉伸一下窗口,控件跳回布局位置,"看起来没生效"的真相是被布局覆盖。规则:进了布局就别手动定位,二选一。
|
||
【随堂练 10 分钟】把自己计算器的布局改成"显示屏占 1 份、键盘占 3 份"的比例(伸缩因子)。
|
||
【过渡】"位置管好了,看控件本身还有什么花样。" """
|
||
},
|
||
{
|
||
'badge': '下午 · 讲授 13:30–16:00',
|
||
'title': '常用控件 + 资源系统 .qrc',
|
||
'boxes': [
|
||
('concept', None, [
|
||
'QLabel 一个控件多种形态:纯文本 / 富文本 HTML / 图片 QPixmap / 动图 QMovie',
|
||
'输入靠 QLineEdit;按钮族 QPushButton / QRadioButton / QCheckBox',
|
||
':/... 路径是 Qt 资源系统:资源编译期打包进可执行文件,发布不用带散图',
|
||
]),
|
||
('pitfall', None, [
|
||
'.qrc 路径与代码引用不一致:运行时静默失败(图不显示),不报编译错误',
|
||
'忘接 CMake 的 AUTORCC / qt5_add_resources——资源系统不生效',
|
||
]),
|
||
],
|
||
'source': '出处:p03/ch08/label_text_html.cpp · label_pixmap.cpp · label_movie.cpp',
|
||
'minutes': 25, 'segment': '下午 · 控件与资源',
|
||
'notes': """【讲法】三个 label 示例连跑(文本→HTML→图片/动图),节奏快一点,每个 5 分钟:控件 API 本身不难,难的是"知道有什么可用",本页目标是建目录索引不是背 API。
|
||
【qrc 重点】打开 label_pixmap.qrc 和对应 cpp 对照讲:qrc 里登记路径 → 代码里 :/ 引用 → CMake AUTORCC 编译打包。坑卡演示:把代码里的 :/ 路径改错一个字母再跑,不报错、图消失,"静默失败"要亲眼见过才会警惕(Day4 的 open()/ParseError 静默失败是同一主题)。
|
||
【互动】问:"图片放资源系统 vs 放程序旁边的目录,各适合什么?"只读小资源进 qrc;用户数据/大文件放文件系统(Day4 的主题,顺势预告)。
|
||
【过渡】"功能齐了,长得还不行。最后一节,化妆。" """
|
||
},
|
||
{
|
||
'badge': '下午 · 讲授 13:30–16:00',
|
||
'title': 'QSS 界面美化',
|
||
'code': 'QPushButton#okButton { background-color: #2f855a; border-radius: 6px; }\nQPushButton#okButton:hover { background-color: #276749; }',
|
||
'boxes': [
|
||
('concept', None, [
|
||
'语法脱胎于 CSS:类型选择器 / ID 选择器(#objectName)/ 伪状态(:hover :pressed)+ 层叠',
|
||
'作用对象是 QWidget 树而非 DOM;「长什么样」与「做什么」分离,改样式不碰逻辑',
|
||
]),
|
||
('pitfall', None, [
|
||
'ID 选择器必须先 setObjectName()',
|
||
'setStyleSheet() 级联影响所有子控件——范围失控最易踩,样式尽量写窄(带选择器)',
|
||
]),
|
||
],
|
||
'source': '出处:examples/qss_styling/qt_qss_styling.cpp(README 有练习方向)',
|
||
'minutes': 25, 'segment': '下午 · QSS',
|
||
'notes': """【讲法】跑 qss_styling 示例,然后当堂改样式立即重跑,"改外观不碰逻辑"的分离感是本节要传达的核心体验。学过 CSS 的学员点一句"就是 CSS 的方言",没学过的按"选择器=谁、属性=怎么画"从零讲。
|
||
【坑卡演示】级联失控:在顶层 widget 上裸写 background-color(不带选择器),跑起来所有子控件全变色,然后加上类型选择器收窄。让学员记"样式写窄"三个字。
|
||
【伪状态】:hover/:pressed 是"零代码交互反馈",性价比极高,今晚作业的验收点之一。
|
||
【随堂练 15 分钟】给上午的主窗口版计算器套第一版 QSS(哪怕只改按钮底色),作业时段继续精修。
|
||
【过渡】"今晚作业就是把它做完做好,验收标准在下一页。" """
|
||
},
|
||
{
|
||
'badge': '下午 · 课堂作业 16:00–17:00(工程师答疑)',
|
||
'title': '任务卡 2 · 用 QSS 美化计算器',
|
||
'boxes': [
|
||
('task', '任务卡 2', [
|
||
'目标:数字键/运算键/等号键用 QSS 区分颜色;整体布局随窗口缩放自适应',
|
||
'验收:至少 2 类按钮不同 background-color;至少 1 个按钮有 :hover 或 :pressed',
|
||
'验收:拉伸窗口按钮跟着重排',
|
||
'参考:examples/qss_styling/ · p03/ch08/custom_widget.cpp',
|
||
]),
|
||
],
|
||
'minutes': 60, 'segment': '下午 · 作业',
|
||
'notes': """【实现提示(开工前讲 3 分钟)】三类按键区分的正确姿势:按类别 setObjectName(或用动态属性),QSS 里 ID/属性选择器分别上色。不要给每个按钮单独 setStyleSheet(层级混乱且难维护)。
|
||
【巡场检查点】①样式写太宽把显示屏也染色了(下午坑卡现场版);②hover 无效,大概率 objectName 没设或拼错,静默失败,让学员自己按坑卡排查。
|
||
【分层】快的学员挑战"深色主题"整套配色,或给等号键加 :pressed 按下变暗的两态反馈。
|
||
【预告】明天计算器线暂停一天(事件/绘图/文件),Day5 综合案例会用到今天所有的骨架加布局加美化技能。"""
|
||
},
|
||
{
|
||
'badge': '当日收尾',
|
||
'title': '当日产出验证',
|
||
'bullets': [
|
||
'现场演示:主窗口版计算器(菜单/停靠窗)+ QSS 配色 + 缩放自适应,功能不回退',
|
||
'钩子:QSS 与前端 CSS 同源;Qt Designer / QML 是另一条 UI 路线(知道存在即可)',
|
||
'明天 Day4:事件机制 / 绘图 / 表格 + 文件系统——数据要开始落盘了',
|
||
],
|
||
'minutes': 5, 'segment': '收尾',
|
||
'notes': """【验收】抽 2–3 组演示升级后的计算器,其他人对照验收标准自查打钩。
|
||
【钩子讲法】一句话安放 Designer/QML:"拖拽和声明式是另外两条 UI 路线,本课程走纯代码是为了让你们看清每一层。会走路了再学开车不迟。"
|
||
【收线】"三天了,你们的程序还没碰过磁盘。明天开始,让数据活过程序的重启。"散场。"""
|
||
},
|
||
]
|