1

I have N buttons on an FLTK window. I'd like to know which button has been pressed by the user. How can I pass a number to each button's lambda callback function? So far, I tried this:

int main(int argc, char **argv) {
  Fl_Window *w_main = new Fl_Window(640, 480);

  std::vector<int> nums;
  for (int i = 0; i < 5; i++) {
    nums.push_back(i);

    Fl_Button *btn1 = new Fl_Button(50 * i, 0, 50, 50);
    char const *num = std::to_string(i).c_str();
    btn1->label(num);
    btn1->callback(
        [](Fl_Widget *w, void *data) { std::cout << "Clicked on: " << (int)data << std::endl; },
        &nums[i]);
  }
  w_main->show();
  return Fl::run();
}

Unfortunately, I get random-like numbers on the output (I assume some random memory garbage interpreted as integers.)

Could you please help me find a solution? Thanks!

G. C.
  • 45
  • 6

3 Answers3

2

When you register your callback:

btn1->callback(
        ...,
        &nums[i]);

You are passing the address of nums[i] as your callback argument (as opposed to the value itself). So you need to interpret data as said address, and dereference it to access the underlying number.

That means:

btn1->callback(
        [](Fl_Widget *w, void *data) { std::cout << "Clicked on: " << *reinterpret_cast<int*>(data) << std::endl; },
        &nums[i]);

The "random" numbers you see are anything but random. They represent the exact location in memory where your number is located. Explicitely converting an address into a numerical value is perfectly valid code, and that's exactly what you did.

  • The answer is correct. If you want to avaois casting, you can also used `Fl_Widget::argument()` for passing an integer user data argument. https://www.fltk.org/doc-1.4/classFl__Widget.html#a217b954bb7a3c053850de3234fce3e62 – Matthias Melcher Apr 04 '22 at 00:01
0

You may use also the coordinates of the widget in order to get the button's number, since you decided the positions of the buttons.

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Button.H>

#include <iostream>
#include <vector>

void show(Fl_Widget *w){
     std::cout << w->x()/50+1 << std::endl; 
}

int main(int argc, char **argv) {


  Fl_Window *w_main = new Fl_Window(640, 480);

  Fl_Button* bts[5];
  for (int i = 0; i < 5; i++) {
    char *num = new char;
    std::strcpy (num, std::to_string(i+1).c_str());

    bts[i] = new Fl_Button(50 * i, 0, 50, 50,num);
    
    bts[i]->callback(show);
  }
  w_main->show();
  return Fl::run();
}

In this way you do not have to pass anything to the callback.

Eddymage
  • 1,008
  • 1
  • 7
  • 22
0

There are 2 additional solutions:

  1. convert a capturing lambda to a function pointer - this will probably work in your case,

  2. using some kind of a functor container and setting that as data, then casting and invoking this functor container in the callback.

There is a video demonstrating point 2, but to recap, you need to choose what kind of functor container to use, you can use std::function<>, but you need to store an instance of it somewhere (say, as a class member), or you can use an anyfunc-like container, which is a kind of invokable std::any. You can then store these in, say, a std::list<anyfunc>, which may unclutter your code a bit.

user1095108
  • 14,119
  • 9
  • 58
  • 116