#include
<FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
using namespace std;
void but_cb( Fl_Widget* , void* ); //callback function
prototype
//------------------------------------------
void make_window() {
Fl_Window* win= new Fl_Window(300,200, "Testing");
win->begin();
Fl_Button* but = new Fl_Button( 10, 150,
70, 30, "Click me");
win->end();
but -> callback( (Fl_Callback*) but_cb );
win->show();
}
//--------------------------------------------
void but_cb( Fl_Widget* o , void* ) {
o->label("Good job"); //redraw not necessary
o->resize(10,150,140,30); //redraw needed
o->redraw();
}
//--------------------------------------------
int main() {
make_window();
return Fl::run();
}
Let's analyse this code
Fl_Window*
win= new Fl_Window(300,200, "Testing");
creates a pointer to a new window object setting the width, height and
the name in the title bar. Note: this object is allocated from heap
memory.
win->begin();
this line is optional but I recommend using it as it makes the code
easier to read. It's purpose is to say whatever new widgets that are
created will be added as children to the current Fl_Window that was
just created. Until you reach win->end(). Don't forget the ->end();
Fl_Button*
but = new Fl_Button( 10, 150, 70, 30, "Click me");
this line creates a button with input parameters (x,
y, width, height, label) where x, y are the position in pixels relative
to the top left corner (0,0). Note: this button is now a child of
the parent
window. This means that the button is the first child of the window
with index 0. See the Fl_Group for
more info on children. Also, note that only objects derived from
Fl_Group have this child parent relationship. Fortunately, Fl_Window is derived from Fl_Group. A BIG
benefit of this structure is that you only need to destroy the
Fl_Window (the parent). Doing so will automatically delete all the
children of the window. So if we delete 'win', then 'but' will be
deleted automatically. This is a very beautiful aspect of FLTK.
win->end();
this line sets the current group to the parent of the window (which in
this case is null since the window has no parent)
but
-> callback( (Fl_Callback*) but_cb
);
This line is important. Callbacks are a means of executing code when an
event occurs. This is the
basis for GUI programming. Usually an event is a mouse click, return
key pressed etc.. There is more to be said about events but I won't go
into it here. However, for this example we wish to change some of the
properties of the the button when a user clicks on it.
First let's look at
the prototype of the callback member function of the Fl_Widget class:
Remember 'but' points to an
Fl_Button which is derived from Fl_Widget so it can use it's base
classes callback member
function .
void Fl_Widget::callback(Fl_Callback*, void* = 0)
Notice the second parameter is optional. That's why our example works.
The second parameter (void*) is for userdata (any data structure you
wish to pass that the callback code might need). 'but_cb' is the name of our callback
function. BTW it's good convention, for code readability, to end
or start callback function names with 'cb'.
It must accept an Fl_Widget* and a void*. Also, we cast the function
name (which is a pointer) to an Fl_Callback* type to satisfy the
Fl_Widget::callback input parameter. Therefore, the Fl_Widget*
which gets passed in this case is the button pointer 'but'. More about
userdata later.
win->show();
This line puts the window on the screen. In other words, it makes it
visible.
void but_cb( Fl_Widget* o , void* ) {
This line just declares our callback function. Note the
missing void* variable. We don't need it here, since we passed null by
default.
o->label("Good
job");
This line changes the displayed label of the button. Note: label() and
value() are the only two widget members that automatically call redraw.
Everything else requires a manual call of redraw(). As you will see in
the next two lines.
o->resize(10,150,140,30);
This line resizes the button. The parameters indicate the button
keeps it's position but the width is doubled from 70 to 140. What's
important is that this member function will NOT redraw the widget. So
you will not see the change unless redraw() is called. Hence, the next
line.
o->redraw();
This line redraws the widget. Needed for the resize().
return Fl::run();
This line is part of most GUI toolkits. It sends the program into the
main event loop. In other words, the program waits for events to
happen. The function run() returns 0 and ends the program when
all windows are closed or hidden. Another quick and easy way to
end the program is to call exit(0). More on this later.
Simple
Window with widgets that talk to each other
One of the
main reasons for this tutorial was that I wanted to show how widgets
(buttons, input boxes, output boxes, etc..) can communicate with
each other in a window. You might say, "what are you talking about
?" Well, most of the examples available simply instantiate
(create) a window object in a function and add some widgets to
it. But this makes it a little difficult to send information from one
widget to another. First off, in order to communicate we need at least
two widgets. Take a
look at the following program it's like the previous one with some
things added. Again try compiling it yourself.
#include
<FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Output.H>
#include <cstdlib>
//for exit(0)
using namespace std;
void copy_cb( Fl_Widget* , void* ); //function prototypes
void close_cb( Fl_Widget* , void* );
void make_window();
int main() {
make_window();
return Fl::run();
}
void make_window() {
Fl_Window* win= new Fl_Window(300,200, "Testing 2");
win->begin();
Fl_Button* copy = new Fl_Button(
10, 150, 70, 30, "C&opy"); //child 0
Fl_Button* close = new Fl_Button(100,
150, 70, 30, "&Quit"); //child 1
Fl_Input* inp
= new Fl_Input(50, 50, 140, 30, "In");
//child 2
Fl_Output* out = new
Fl_Output(50, 100, 140, 30, "Out"); //child 3
win->end();
copy->callback( (Fl_Callback*) copy_cb );
close->callback( (Fl_Callback*) close_cb);
win->show();
}
void copy_cb( Fl_Widget* o , void* ) {
Fl_Button* b=(Fl_Button*)o;
const char* temp;
temp = (
(Fl_Input*)(b->parent()->child(2)) )->value();
( (Fl_Output*)(b->parent()->child(3))
)->value(temp);
}
void close_cb( Fl_Widget* o, void*) {
exit(0);
}
----------------------------------------------------------------------------
When run this program should look like this

Notice the Copy
and Quit have
underlined letters. These buttons can be invoked by pressing
ALT-c and ALT-q. This was achieved by simply placing an "&"
in front of the letter in the label parameter in the constructor.
This program just copies whatever is in "In" to "Out" when "Copy" is
pressed. The purpose though is to show communicating widgets.
Let's look at the copy_cb callback
function.
void copy_cb( Fl_Widget* o ,
void* ) {
Fl_Button*
b=(Fl_Button*)o;
Notice 'o' is an Fl_Widget
input parameter but 'copy_cb'
is an Fl_Button callback function, so 'o'
refers to an Fl_Button. Therefore, we declare an Fl_Button
pointer called 'b' and cast 'o' to it. Done.
Next comes the ugly looking communication.
temp =
( (Fl_Input*)(b ->parent()->child(2))
)->value();
( (Fl_Output*)(b
->parent()->child(3)) )->value(temp);
Get the parent() of Fl_Button
b which returns an Fl_Group*.
That's good because child(2)
is a member function of an Fl_Group, so no cast is required. Then
child(2) returns an Fl_Widget* of the 2nd widget created. But the
2nd child is an Fl_Input, so we need a cast. Once the cast is done we
can THEN call value() which
returns the const char* of the text typed in by the user. Then this
const char* gets assigned to a temporary variable temp.
The next line just sets the temp
variable to the value of the Fl_Output widget out in the same fashion as
the line above.
I must warn you that this is not a good way to do widget communication.
For one thing it's ugly and hard to read. Second you must manually keep
track of the widget indices (ie 0,1,2,3 etc..) Third, there is no range
checking on the child(int n).
So if this method of communication sucks, how do we do it???
Answer: Use C++ to make a wrapper class. See next section.
This next program produces the exact same GUI interface as before but
it's coded completely
different. I recommend this method. You will see why.
#include
<FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Output.H>
#include <cstdlib> // for
exit(0)
using namespace std;
//---------------------------------------------------
class SimpleWindow : public Fl_Window{
public:
SimpleWindow(int w, int h, const char*
title );
~SimpleWindow();
Fl_Button* copy;
Fl_Button* quit;
Fl_Input* inp;
Fl_Output* out;
private:
static void cb_copy(Fl_Button*, void*);
inline void cb_copy_i(Fl_Button*,
void*);
static void cb_quit(Fl_Button*, void*);
inline void cb_quit_i(Fl_Button*,
void*);
};
//----------------------------------------------------
int main (){
SimpleWindow win(300,200,"Testing 3");
return Fl::run();
}
//----------------------------------------------------
SimpleWindow::SimpleWindow(int w, int h, const char*
title):Fl_Window(w,h,title){
begin();
copy = new Fl_Button( 10, 150, 70, 30,
"C&opy");
copy->callback((Fl_Callback*)cb_copy,
this);
quit = new Fl_Button(100, 150, 70, 30,
"&Quit");
quit->callback((Fl_Callback*)cb_quit,
this);
inp = new Fl_Input(50, 50, 140, 30,
"Input:");
out = new Fl_Output(50, 100, 140, 30,
"Output:");
end();
resizable(this);
show();
}
//----------------------------------------------------
SimpleWindow::~SimpleWindow(){}
//----------------------------------------------------
void SimpleWindow::cb_copy(Fl_Button* o, void* v) {
SimpleWindow* T=(SimpleWindow*)v;
T->cb_copy_i(o,v);
}
//---------------------------------------------------
void SimpleWindow::cb_copy_i(Fl_Button* , void*) {
out->value(inp->value());
}
//----------------------------------------------------
void SimpleWindow::cb_quit(Fl_Button*o , void* v) {
SimpleWindow* T=(SimpleWindow*)v;
T->cb_quit_i(o,v);
}
//----------------------------------------------------
void SimpleWindow::cb_quit_i(Fl_Button* , void*) {
exit(0); // or hide();
}
//----------------------------------------------------
Okay let's analyse this improved version.
The first thing you will notice is that I have created a class called
SimpleWindow which is derived from an Fl_Window. However, I have
added public pointer members of all the widgets I want to add to my
window. Since these pointers are public I can access them outside of
the class if I need to. Now lets look at how callbacks are done in
classes.
private:
static
void cb_copy(Fl_Button*, void*);
inline void
cb_copy_i(Fl_Button*, void*);
//----------------------------------------------------------------
void
SimpleWindow::cb_copy(Fl_Button* o, void* v) {
SimpleWindow* T=(SimpleWindow*)v;
T->cb_copy_i(o,v);
}
void
SimpleWindow::cb_copy_i(Fl_Button* , void*) {
out->value(inp->value());
//notice how clean this communication is compared to the last section
}
//----------------------------------------------------------------
These two functions are important. They go together. They are
the method for having member function callbacks. First I would like to
say that callbacks in a class can only be static.
In other words the THIS pointer does not exist. So the way to overcome
this is by having two (yes that's right) functions. Where the static
function calls a second member function of the class that DOES have
the THIS pointer initialized. So you can access all data members and
member functions of the class with the implicit THIS pointer. Now look
here. If you think this is weird, let me remind you that FLTK does not
use a precompiler as other toolkits do. Plus if you really only want to
have one function, you can.
For example: the static cb_copy
callback function could have been
void
SimpleWindow::cb_copy(Fl_Button* o, void* v) {
SimpleWindow* thz = (SimpleWindow*)v;
thz->out->value(thz->inp->value());
}
But this makes things more difficult to read with all the ->
operators. Plus, don't forget, since the second function is inlined
there really is only one function call. So there is no penalty for
making two functions. Note the second function has an '_i' added to the end of it to
denote that it's inlined.
Get/Set methods
One aspect of FLTK which might take some getting use to is the get/set
functions. They both have the same name but are overloaded with respect
to their return and input parameters. Get functions have no input
parameter, so inp->value() gets
or returns the value of the widget (a const char* in this case). On the
other hand, out->value(const
char*) sets the value of the out
object. Once I got used to reading code this way I found it to be
cleaner. Also, compare how clean and efficient this sinlge line
of communication is compared to the two ugly lines in the previous
example. No more messy counting children or casting. This is a
consequence of making the wrapper class. Simple, clean, efficient.
Also notice I don't have to put a pointer in front of begin() or end()
or show() etc...
as the THIS pointer is implicit.
copy->callback((Fl_Callback*)cb_copy,
this);
Now this line utilizes the userdata void* I
was talking about before. In this case, as in most, I pass the address
of the class instance (the THIS pointer). Therefore, I have access to
the entire class in
the callback!! You gotta love it. Just in case you forgot about void*
in C++, here is a little refresher.
Void Pointers:
Some of you that are new to C++ may have not seen void pointers
before. Basically, a void* is a pointer that can point to
anything. Usually pointers are typed, in other words, you know the type of data to which they point.
But a void* has no type. We do some casting in the
callback function to deal with this issue.
From another perspective, pointers usually know the size of the object
to which they are pointing. But void pointers don't, they just contain
the address. Therefore, one can never dereference a void*. Therefore,
we must utilize some casting to do the job.
resizable(this);
This line allows the program window to
be resized. However, I could have just as easily called
resizable(copy) which would make the window resizable also. The
difference being that my copy button would have been the widget to
resize both horizontally and vertically. Whereas, in my case the
whole window gets resized. Remember only one widget per group can be
resizable. So if you want a certain layout behavior you need to add
appropriate horizontal and vertical groups. I still need practice with
this stuff myself so I will just quote an FLTK veteran. This is copied
from message
1375 Dated 17 Jan 2004 from fltk general newsgroup.
Marc R.J. Brevoort wrote:
Here
are a few hints. Read them carefully
then try again. Good luck!
- to make things more predictable, it helps to fill groups with
widgets only in one direction: either horizontally or
vertically.
(this also helps explain the following hints).
- If you need to fill groups both horizontally and vertically,
fill a group WITH GROUPS in one direction, then those
'sub'groups
in the other direction.
- In a group, at most one widget can be set to "resizable".
Attempts to setting several widgets to 'resizable' causes
only the last one to be marked resizable.
- Setting a widget to "resizable" means that that widget can be
resized BOTH horizontally and vertically, not that it is the
only resizable widget in the group.
- all other widgets in the group may resize along proportionally to
the size of the group, but only in one direction (if the
group
is populated horizontally, 'nonresizable' widgets only
resize
vertically, only the 'resizable' widget resizes in both
directions.
- if a group is only resizable in one direction, only the resizable
widget will resize, all other widgets will stay the way
they are.
Hope this helps,
grtz
MRJB
Thank you Marc. I have printed this message and included it with my
FLTK documentation. It's a keeper.
Finally the last line to analyse is
exit(0);
// or hide();
You can quit your programs in
one of two ways. One way is to call exit(0) and depend on your
operating system to free all allocated memory. The other is to
call hide() on all windows
which will cause Fl::run() to
return and destructors to
be called normally. One imporatant thing to keep in mind though is that
if you have global objects then using hide() may not be a good idea as
those objects will not have their destructors called since they were
not
created in the main function scope. But I personally don't like
declaring objects globally anyway. Note: calling exit(0) will not
return Fl::run() but all memory WILL
be freed. Again be aware that I don't delete any of the dynamic objects
created in the class constructor, in the destructor. This is
because SimpleWindow is a sub-class of an Fl_Group and as such it has
all of it's children destroyed by the base class destructor. This is
possible since the base class has the ability to iterate through all
the children with child(int
n) and children().
Who needs Java when
you've got FLTK. ; )
Side note: I would like to post a bit of code Jason Bryan came up with
on FLTK general newsgroup.
void fl_exit()
{
while( Fl::first_window() )
Fl::first_window()->hide();
}
This little function should make sure that all windows hide, therebye,
ensuring that Fl::run() is returned and all destructors called
properly. Thanks Jason.
More sections
to come in the future
As I learn more and more about FLTK I will add new sections to
this site. I have plans to learn layout/resizing stuff. Also, I could
do a section on menus but I held off because 1.2 is going to use a new
and improved menuing system (which I haven't learned yet). I was also
thinking about a little section on makefiles. So
come back once in a while to see if anything new is up.
I teach senior high school Physics and Computer Programming (C++ of
course) in
British Columbia, Canada. Programming is my hobby and passion. I
discovered FLTK in 2003 and I am really happy that Open Source/Free
Software like this exists. FLTK has opened up a whole new world of
programming possibilities. I would like to
thank the FLTK general newsgroup usual suspects like Bill, Mike, Matt,
Greg,
Jason, Marc, Alexey and Dejan. Sorry if I left anyone
out. You guys are what keeps FLTK alive and well. BRAVO!
Also please feel free to post comments, suggestions or rate
this site on the
FLTK
links/bazaar tutorial page
that links to this site. Or if you would
like to send me an email. Send it to
fltk_beginner_tutorial@yahoo.com
Robert Arkiletian