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

6.4 KiB
Raw Blame History

第 2 课:按钮与回调

本课目标:在窗口上放一个按钮,点击按钮后文字发生变化——学会"用户点击 → 程序响应"。

复习:第 1 课你做出了一个静态窗口。本课让它"能动"。


1. 这一课要搞懂什么

最重要的新概念:回调(callback)

以前命令行程序是这样跑的:

一行接一行执行 → 程序结束

GUI 程序是这样跑的:

创建窗口和控件 → 进入"等待模式" → 用户点按钮 → 执行回调函数 → 继续等待...

回调函数就是"当按钮被按下时,要执行的那段代码"。就像你告诉机器:"如果有人按这个按钮,就做这件事。"

本课新朋友

FLTK 类/概念 比喻 作用
Fl_Button 按钮 可以点击的控件
callback() 说明书 告诉按钮"被按时做什么"
全局指针 留言板 让回调函数能找到要改的控件

2. 为什么需要"全局指针"?

看下面的问题:

// ❌ 这样不行!
void button_clicked() {
    box->label("抽卡了!");  // 这个函数里不认识 box
}

int main() {
    Fl_Box *box = new Fl_Box(50, 100, 300, 100, "还没抽");
    // box 只在 main 里有效,button_clicked 函数看不到它
}

解决方案:把 box 声明在 main 外面——成为"全局变量",到处都能看到。

// ✅ 这样就行!
Fl_Box *box;  // 全局声明

void button_clicked(Fl_Widget *, void *) {
    box->label("抽卡了!");  // 全局 box,函数里也能用
}

小提示:(Fl_Widget *, void *) 是回调要的固定格式,本课先记住写法,不用深究。


3. 完整示例程序

保存为 lesson_gui_2.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_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. 代码逐行解释

全局变量

Fl_Box *result_box;

放在所有函数外面 → 全局 → 回调函数和 main 都能用。

回调函数

void draw_callback(Fl_Widget *w, void *data) {
    result_box->label("你点击了抽卡按钮!");
    result_box->labelcolor(FL_RED);
}
  • Fl_Widget *w —— 被点击的那个控件(本课先不关心)
  • void *data —— 可以传额外数据(本课先用不上)
  • 这两个参数是 FLTK 要求的固定格式

按钮

Fl_Button *btn = new Fl_Button(130, 40, 140, 50, "🎴 抽一张!");
btn->callback(draw_callback);
  • 位置 (130, 40),宽 140,高 50
  • callback(draw_callback) —— 告诉按钮:被按时执行 draw_callback

labelcolor

result_box->labelcolor(FL_RED);
  • FL_RED 是 FLTK 内置的颜色名
  • 其他常用:FL_BLUEFL_GREENFL_BLACKFL_WHITE

5. 运行效果

启动后看到:

┌─────────────── 抽卡世界 ───────────────┐
│                                         │
│        ┌──────────────┐                │
│        │  🎴 抽一张!   │                │
│        └──────────────┘                │
│                                         │
│         请点击按钮抽卡~                  │
│                                         │
└─────────────────────────────────────────┘

点击按钮后:

│        ┌──────────────┐                │
│        │  🎴 抽一张!   │                │
│        └──────────────┘                │
│                                         │
│      你点击了抽卡按钮!  ← 变红了         │
│                                         │

6. 本课课堂小练习

  1. 修改回调函数,让点击后显示"你又抽了一次!"。
  2. 把按钮文字改成"抽一抽"或你自己想的名字。
  3. 放两个按钮:一个"抽卡",一个"重置"。点"重置"让文字变回初始状态。
    • 提示:需要两个回调函数(或一个回调 + data 参数区分)

7. 本课课后作业(选做)

  • 点击按钮 5 次后,文字自动变成"次数用完啦!"(提示:加一个全局 int count 变量,在回调里累加并判断)
  • 试试 FL_BLUEFL_GREENfl_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