Files
chouka/第02课-按钮与回调.md
T
2026-05-16 12:05:04 +08:00

237 lines
7.4 KiB
Markdown
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.
# 第 2 课:按钮与回调
> **本课目标**:在窗口里放一个按钮,**点一下文字会变**。理解 GUI 程序最核心的套路:**用户操作 → 程序响应**。
>
> **预计时间**35~45 分钟
>
> **学完之后**:你能解释什么是回调函数,并能让按钮触发任意行为。
---
## 0. 上课前先理理
第 1 课你做出了一个**静态**窗口——打开后就不变了。本课让它"活"起来。
### 这是 GUI 最大的脑回路转弯
```
命令行: 从上到下跑完每一行 → 程序结束
GUI: 创建好窗口和按钮 → 进入"等待模式" → 用户点 → 跑回调函数 → 继续等 ...
```
GUI 程序大部分时间在**发呆等**,只有用户点鼠标时才动一下。"动一下做什么"由你写的**回调函数**决定。
### 这一课认识 3 个新朋友
| 名字 | 比喻 | 干什么用 |
|---------------|------------------|-----------------------------------------|
| `Fl_Button` | **按钮** | 可以被点击的控件 |
| `callback` | **说明书** | "按这个按钮要做什么"的那段代码 |
| **全局指针** | **留言板** | 让回调函数能找到要修改的控件 |
---
## 1. 起步:先编译空骨架
打开 `lesson_gui_2.cpp`(已经把第 1 课的窗口写好了),切到第 2 课编译:
```bash
cmake -B build -DLESSON=2 # 切到第 2 课
cmake --build build && ./build/gacha2
```
### ✅ 检查点 1
- [ ] 看到一个 400×300 的空窗口,标题 "抽卡世界 - 第2课"
- [ ] 窗口里什么都没有(按钮和文字都还没加)
---
## 2. 步骤 1:先放一个按钮(点了没反应也没关系)
### 🤔 先想一下
- 按钮和窗口、便利贴一样都是控件——它要不要 `new`
- 按钮和便利贴比,参数列表多了什么?少了什么?
### ✏️ 改两个地方
**(1) 顶上加 include**(替换对应 TODO):
```cpp
#include <FL/Fl_Button.H>
```
**(2) 在 `main` 里、`window->end()` 之前加按钮**
```cpp
Fl_Button *btn = new Fl_Button(130, 40, 140, 50, "抽一张!");
btn->labelsize(18);
```
读法:`new` 出一个按钮,位置 (130, 40),宽 140、高 50,文字 "抽一张!"。
### ▶️ 编译运行
```bash
cmake --build build && ./build/gacha2
```
### ✅ 检查点 2
- [ ] 窗口里出现了一个按钮,写着 "抽一张!"
- [ ] 鼠标移上去会变色(按钮的反馈)
- [ ] 点一下……什么也没发生 ← 正确!因为我们还没告诉它做什么
---
## 3. 步骤 2:放一个文字框,先静态显示
按钮按了要让什么变?得先有个"被改的人"。
### ✏️ 在按钮代码下面加文字框
```cpp
Fl_Box *result_box = new Fl_Box(50, 130, 300, 100, "请点击按钮抽卡~");
result_box->labelsize(20);
```
(注意:现在 `result_box` 还只是 `main` 里的**局部变量**——下一步会把它升级成全局)
### ▶️ 编译运行
### ✅ 检查点 3
- [ ] 看到按钮和"请点击按钮抽卡~"两段文字
- [ ] 点按钮还是没反应(正常)
---
## 4. 步骤 3:写回调,让点击真的改文字 ⭐核心⭐
这是本课最关键的一步。
### 🤔 先想一下这个看似简单的问题
我们想要:**点按钮 → 改 `result_box` 的文字**。
很自然的写法是:
```cpp
void on_click() {
result_box->label("你点击了抽卡按钮!"); // ← 编译器在这里报错!
}
```
为什么?因为 `result_box``main` 函数里的局部变量,`on_click` 这个外部函数**看不到它**。
### 💡 解决方案:把 `result_box` 变成"全局留言板"
`result_box` 的声明拿出 `main`,放到所有函数外面 → 任何函数都能用到它。
### ✏️ 改 3 个地方
**(1) 在所有函数之外(include 下面),声明全局变量**(替换对应 TODO):
```cpp
Fl_Box *result_box;
```
**(2) 在 `main` 之外也写一个回调函数**(替换对应 TODO):
```cpp
void draw_callback(Fl_Widget *w, void *data) {
result_box->label("你点击了抽卡按钮!");
result_box->labelcolor(FL_RED); // 顺便把字变红
}
```
> 📌 `(Fl_Widget *w, void *data)` 是 FLTK 规定的回调函数固定写法,**不能改**。即使你不用 `w` 和 `data`,也得写上。
**(3) 把 `main` 里原来的局部声明**
```cpp
Fl_Box *result_box = new Fl_Box(50, 130, 300, 100, "请点击按钮抽卡~");
```
去掉前面的 `Fl_Box *`(因为已经在全局声明过类型),改成:
```cpp
result_box = new Fl_Box(50, 130, 300, 100, "请点击按钮抽卡~");
```
**(4) 在 `btn->labelsize(18);` 后面加一行,把按钮和回调连起来**:
```cpp
btn->callback(draw_callback);
```
读法:把 `draw_callback` 这本"说明书"交给按钮,按它的时候就照着做。
### ▶️ 编译运行
```bash
cmake --build build && ./build/gacha2
```
### ✅ 检查点 4
- [ ] 启动后看到 "请点击按钮抽卡~"
- [ ] 点一下按钮 → 文字变成 "你点击了抽卡按钮!",**而且是红色的**
- [ ] 多点几次……文字一直是红色,没变化(因为我们写的是固定文字)
🎉 **第 2 课通关!** 你完成了 GUI 编程最重要的一步:把用户操作和代码反应连起来。
---
## 5. 玩一玩:改一改看会发生什么
每改一个,**保存 → 编译 → 运行**:
1. 把回调里的文字改成 "你又抽了一次!"。
2.`FL_RED` 换成 `FL_BLUE``FL_GREEN``FL_MAGENTA`
3. 试试自定义颜色:`result_box->labelcolor(fl_rgb_color(255, 128, 0));` ← 橙色。
4. 把按钮文字改成你自己起的名字。
5. 在回调里**再加一行** `result_box->labelsize(30);`,点按钮后字会变更大吗?
6. **思考题**:如果把 `btn->callback(draw_callback);` 这行注释掉,会怎样?
---
## 6. 课后挑战(选做)
挑一个:
- **A. 双按钮**:再加一个按钮 "重置",点了让 `result_box` 恢复成 "请点击按钮抽卡~",颜色变回黑色 (`FL_BLACK`)。
- 提示:需要再写一个回调函数,比如 `reset_callback`,然后给第二个按钮 `callback(reset_callback)`
- **B. 计次按钮**:点 5 次之后文字自动变成 "次数用完啦!"。
- 提示:在所有函数外加 `int count = 0;`,回调里 `count++;` 并判断。
- **C. 思考题**:GUI 程序为什么不按"先执行第一行,再执行第二行"的固定顺序跑?回调函数是被**谁**叫起来的?
---
## 7. 卡住了?常见小问题
| 现象 | 多半是 |
|------|-------|
| 点按钮没反应 | 忘了 `btn->callback(draw_callback);` |
| 编译报 "`result_box` 未声明" | 全局声明那行漏了,或者写到 `main` 里去了 |
| 编译报 "回调函数参数太少" | `(Fl_Widget *w, void *data)` 这两个参数不能省 |
| 文字变了但颜色没变 | `labelcolor` 拼错了,或者用了不存在的颜色名 |
| 红字看起来糊糊的 | 正常的,FLTK 默认抗锯齿如此 |
---
## 8. 偷看老师答案
```bash
cmake -B build -DLESSON=2 -DUSE_SOLUTION=ON
cmake --build build && ./build/gacha2
```
源文件路径:`solutions/lesson_gui_2.cpp`
---
## 9. 下一课预告
第 3 课把**随机数**拉进来:点按钮 → 随机出"普通卡 / 稀有卡 / 超稀有卡",**不同稀有度配不同颜色**。这才是真正的抽卡。
> 📝 学完记得在 `学习进度与状态-GUI.md` 里把第 2 课打勾。
**下一课**`第03课-随机抽卡.md`