# 第 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 ``` **(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`