# 第 2 课:按钮与回调 **本课目标**:在窗口上放一个按钮,点击按钮后文字发生变化——学会"用户点击 → 程序响应"。 **复习**:第 1 课你做出了一个静态窗口。本课让它"能动"。 --- ## 1. 这一课要搞懂什么 ### 最重要的新概念:回调(callback) 以前命令行程序是这样跑的: ``` 一行接一行执行 → 程序结束 ``` GUI 程序是这样跑的: ``` 创建窗口和控件 → 进入"等待模式" → 用户点按钮 → 执行回调函数 → 继续等待... ``` **回调函数**就是"当按钮被按下时,要执行的那段代码"。就像你告诉机器:"如果有人按这个按钮,就做这件事。" ### 本课新朋友 | FLTK 类/概念 | 比喻 | 作用 | |-------------|------|------| | `Fl_Button` | 按钮 | 可以点击的控件 | | `callback()` | 说明书 | 告诉按钮"被按时做什么" | | 全局指针 | 留言板 | 让回调函数能找到要改的控件 | --- ## 2. 为什么需要"全局指针"? 看下面的问题: ```cpp // ❌ 这样不行! void button_clicked() { box->label("抽卡了!"); // 这个函数里不认识 box! } int main() { Fl_Box *box = new Fl_Box(50, 100, 300, 100, "还没抽"); // box 只在 main 里有效,button_clicked 函数看不到它 } ``` **解决方案**:把 `box` 声明在 `main` 外面——成为"全局变量",到处都能看到。 ```cpp // ✅ 这样就行! Fl_Box *box; // 全局声明 void button_clicked(Fl_Widget *, void *) { box->label("抽卡了!"); // 全局 box,函数里也能用 } ``` > 小提示:`(Fl_Widget *, void *)` 是回调要的固定格式,本课先记住写法,不用深究。 --- ## 3. 完整示例程序 保存为 `lesson_gui_2.cpp`: ```cpp #include #include #include #include // 全局变量——让回调函数也能访问 Fl_Box *result_box; // 回调函数——按钮被按时执行 void draw_callback(Fl_Widget *w, void *data) { result_box->label("你点击了抽卡按钮!"); result_box->labelcolor(FL_RED); // 文字变红 } int main() { Fl_Window *window = new Fl_Window(400, 300, "抽卡世界 - 第2课"); // 在上方放一个按钮 Fl_Button *btn = new Fl_Button(130, 40, 140, 50, "🎴 抽一张!"); btn->labelsize(18); btn->callback(draw_callback); // 绑定回调函数 // 在下方放一个显示结果的标签 result_box = new Fl_Box(50, 130, 300, 100, "请点击按钮抽卡~"); result_box->labelsize(20); window->end(); window->show(); return Fl::run(); } ``` 配套 `CMakeLists.txt`: ```cmake cmake_minimum_required(VERSION 3.14) project(gacha2 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) if(MSVC) add_compile_options("/utf-8") endif() find_package(FLTK CONFIG REQUIRED) add_executable(gacha2 lesson_gui_2.cpp) target_link_libraries(gacha2 PRIVATE fltk) ``` Mac 编译:`cmake -B build && cmake --build build && ./build/gacha2` --- ## 4. 代码逐行解释 ### 全局变量 ```cpp Fl_Box *result_box; ``` 放在所有函数外面 → 全局 → 回调函数和 main 都能用。 ### 回调函数 ```cpp void draw_callback(Fl_Widget *w, void *data) { result_box->label("你点击了抽卡按钮!"); result_box->labelcolor(FL_RED); } ``` - `Fl_Widget *w` —— 被点击的那个控件(本课先不关心) - `void *data` —— 可以传额外数据(本课先用不上) - 这两个参数是 FLTK 要求的固定格式 ### 按钮 ```cpp Fl_Button *btn = new Fl_Button(130, 40, 140, 50, "🎴 抽一张!"); btn->callback(draw_callback); ``` - 位置 (130, 40),宽 140,高 50 - `callback(draw_callback)` —— 告诉按钮:被按时执行 `draw_callback` ### labelcolor ```cpp result_box->labelcolor(FL_RED); ``` - `FL_RED` 是 FLTK 内置的颜色名 - 其他常用:`FL_BLUE`、`FL_GREEN`、`FL_BLACK`、`FL_WHITE` --- ## 5. 运行效果 启动后看到: ``` ┌─────────────── 抽卡世界 ───────────────┐ │ │ │ ┌──────────────┐ │ │ │ 🎴 抽一张! │ │ │ └──────────────┘ │ │ │ │ 请点击按钮抽卡~ │ │ │ └─────────────────────────────────────────┘ ``` 点击按钮后: ``` │ ┌──────────────┐ │ │ │ 🎴 抽一张! │ │ │ └──────────────┘ │ │ │ │ 你点击了抽卡按钮! ← 变红了 │ │ │ ``` --- ## 6. 本课课堂小练习 1. 修改回调函数,让点击后显示"你又抽了一次!"。 2. 把按钮文字改成"抽一抽"或你自己想的名字。 3. **放两个按钮**:一个"抽卡",一个"重置"。点"重置"让文字变回初始状态。 - 提示:需要两个回调函数(或一个回调 + `data` 参数区分) --- ## 7. 本课课后作业(选做) - 点击按钮 5 次后,文字自动变成"次数用完啦!"(提示:加一个全局 `int count` 变量,在回调里累加并判断) - 试试 `FL_BLUE`、`FL_GREEN`、`fl_rgb_color(255, 128, 0)`(自定义橙色) - 思考题:为什么 GUI 程序不按"先执行第一行,再执行第二行"的固定顺序跑? --- ## 8. 常见小问题 - **点按钮没反应**:检查 `callback()` 那一行有没有写,函数名有没有拼错。 - **label 变了但颜色没变**:检查 `labelcolor()` 的拼写。 - **`result_box` 未声明**:检查是否在 `main` 外面写了 `Fl_Box *result_box;`。 - **回调函数的参数**:`(Fl_Widget *w, void *data)` 这个写法不能省略参数;即使不用,也要写上。 --- ## 9. 下一课预告 第 3 课把**随机数**加进来:点击按钮 → 随机出普通 / 稀有 / 超稀有——这才是真正的抽卡! --- *学完请在 `学习进度与状态-GUI.md` 里更新第 2 课的状态。* **下一课文档**:`第03课-随机抽卡.md`