When you try to use FlTk for your projects, you will quickly encounter the need to use pointers to functions. FlTk uses these a lot as a way to specify the behavior of things.
In FlTk the way you specify the behavior of most things (like what happens when you press a button) is by telling FlTk a function (called a “callback”) that should be called when the thing happens.
So, for example, if you want to have something happen when you press a button, you define a function that does the thing that should happen. And then when you create the button, you tell FlTk to “call me back” when the button is pushed by associating the function with the button.
So, for example, when my window is created I might make a button:
Fl_Button* button = new Fl_Button(100,100,50,20,”Push Me”);
Which you should recognize as making a push button object (at position x,y = 100,100, size 50×20, labeled “push me”). You can read up on it in the documentation.
Now, suppose we want to have this button do something when it is pushed. We might want to write a little function that does that work:
void whenPushed() { printf("Ouch!\n"); }
This should be as easy as telling the button that we want to have this function be its callback when we create it, but if we try to compile this code:
Fl_Button* button = new Fl_Button(10, 10, 70, 20, "Push Me"); button->callback(whenPushed);
C++ will complain with one of its typical cryptic error messages:
1>c:\users\gleicher\projects\train2014\traintutorial\trainwindow.cpp(85): error C2664: 'void Fl_Widget::callback(Fl_Callback1 (__cdecl *),long)' : cannot convert argument 1 from 'void (__cdecl *)(void)' to 'Fl_Callback (__cdecl *)' 1> None of the functions with this name in scope match the target type
The problem is that FlTk expects that a callback function has a particular signature – which is effectively the type of the function. If we look at the definition of Fl_Button::callback (which is actually a method of Fl_Widget), we see that there we need to have our function be of type Fl_Callback. When we look that up, we see that there are actually 3 different versions! The main one is:
typedef void(Fl_Callback )(Fl_Widget *, void *)
What this tells us (in C++ crypticness) is that a callback is a function (that’s why its in parenthesis, and has arguments in parenthesis) that takes two arguments – an Fl_Widget* and a void*. If you aren’t used to seeing the use of void*, that’s good: it’s an old trick from C programming days. It basically means “a pointer that we don’t know what it points to.”
So, to make our code compile, we need to make our function have this signature:
void whenPushed2(Fl_Widget*, void*) { printf("Ouch!\n"); }
actually compiles.
If you do this, you’ll see that it doesn’t quite what we wanted. The callback happens when the button is released. We could have known this would have happened from the documentation. In a more modern toolkit, we could have associated our action with a variety of different events (like button push, button release, …). FlTk doesn’t give us that choice. What it does do is allow us to have the callback called every time anything happens to the button by setting it’s “when” attribute.
This gives us an excuse to understand callbacks a little more – since now our callback will be called both at push and release, we need to figure out which is happening. We do this by looking at the state of the button – it has value 1 when it is down. This means the callback function needs to know about the button. In a modern toolkit, we might do this by making the callback a method of the button (although, that design has other issues). But in FlTk, the callback is passed the widget that caused the callback to happen (in this case the button). Notice the first parameter to the callback function.
So we could try to write:
void whenPushed3(Fl_Widget* w, void*) { if (w->value()) printf("Ouch!\n"); else printf("Ahhh!\n"); }
Except this won’t compile! The generic widget has no value – only buttons do. But FlTk doesn’t know it’s a button – it only knows it’s a widget. We have to tell it that it’s actually a button using a cast. Note: casts are bad. However, the way that FlTk is build you have to use them a lot:
void whenPushed4(Fl_Widget* w, void*) { if ( ((Fl_Button*) w)->value()) printf("Ouch!\n"); else printf("Ahhh!\n"); }
Note that we are telling C++ “forget what the type information tells you. trust us. theButton is really a pointer to an Fl_Button.” We had better be right: if not, the C++ runtime will treat the pointer as a button (and if it isn’t really a button, bad things will happen).
Here, it’s pretty safe. We know that the only time this function will be called is by the button we made. However, we need to be careful not to assign this callback function to some object that isn’t a button.
A different approach is to put the cast someplace different. We can define a function:
void whenPushed5(Fl_Button* theButton, void*) { if (w->value()) printf("Ouch!\n"); else printf("Ahhh!\n"); }
Notice that this is NOT an Fl_Callback! It has the wrong signature. But we know that it is OK (since we’ll only use it for buttons). So we can tell C++ that its OK to trust us.
button->callback( (Fl_Callback*) whenPushed5);
Notice that we’re actually using Fl_Callback*, since we need a pointer to the function (not the function itself). This distinction is (at best) cryptic in C/C++.
What about that 2nd parameter?
Remember the 2nd parameter of the callback? Let’s see what that is useful for…
Suppose we want to have multiple buttons use the same callback. But since each button is different, we need some different information for each one – information we know when we set up the callback. Note: we could stick the information inside the button objects (by subclassing it and adding an additional field). But this is sometimes easier.
The second parameter lets us pass anything we want to the callback function. We tell FlTk what we want to pass when we setup the callback. Because FlTk doesn’t know the type that we’ll want to use, it makes us convert it to a generic pointer (void*). We’ll have to know what the pointer really points to, and tell C++.
So, for example, I’ll make the pointer point to a string (notice the cast!) …
void whenPushed6(Fl_Widget* w, void* p) { printf("%s says:", (char*)p); if (((Fl_Button*)w)->value()) printf("Ouch!\n"); else printf("Ahhh!\n"); }
and then I set it up like…
Fl_Button* b1 = new Fl_Button(10, 50, 70, 20, "One"); b1->when(FL_WHEN_CHANGED); b1->callback(whenPushed6, "Bert"); Fl_Button* b2 = new Fl_Button(10, 80, 70, 20, "Two"); b2->when(FL_WHEN_CHANGED); b2->callback(whenPushed6, "Ernie");
Hopefully, this will help you understand how FlTk callbacks work so that you can add interesting features to the framework code for the projects.