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

223 lines
6.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 课:按钮与回调
**本课目标**:在窗口上放一个按钮,点击按钮后文字发生变化——学会"用户点击 → 程序响应"。
**复习**:第 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 <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Box.H>
// 全局变量——让回调函数也能访问
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`