By Michael Gleicher, September 11, 2005 Modified by Yu-Chi Lai at 2006
We have provided some tutorials for getting started with FlTk. These are very "example oriented" - they show you some FlTk example programs, and expect you to learn from that. The purpose of this document is to give you some insight onto how and why FlTk works, and to give you some ideas on how to use FlTk. You might want to look at the other tutorials first.
The most important resource for FlTk is the documentation. Note: you probably have access to a copy on your local disk as well. Unfortunately, the tutorial chapter starts with an example that is too easy, and quickly escalates to one that is too hard.
The most central concept in FlTk is a widget. A widget is an object that takes up a rectangular area on the screen, knows how to draw itself, and knows how to respond to events. Widgets can contain other widgets. In FlTk, almost everything is a widget. For example, a window is just a special kind of Widget.
Another important concept in FlTk is an event. Programs in FlTk are event driven. That is, they generally sit around waiting for something (an event) to happen. Examples of events are mouse clicks, mouse movements, the mouse entering or exiting a widget, a window being shown, or any number of other things. When an event happens, FlTk figures out which widgets to tell about it.
FlTk uses a mechanism called a callback to allow programmers to give behavior to pre-defined widgets. A callback is a function that gets called at a certain time. For example, a push button widget (FlTk has these built in) needs to know what to do when the user pushes the button. For widgets that are defined by the application programmer (not built into FlTk), the programmer must provide an event handling method for the widget - callbacks are not used.
Simple Fltk programs (like we will write for CS559) usually have a structure like this:
Notice that all of the real work of your program happens when the widgets respond to events.
There are other ways to organize FlTk programs. For example, your program can do computations and periodically check to see if there are events to respond to, and if so pass them to FlTk. For CS559, we strongly encourage you to program in the event driven style.
Since this is a graphics class, you might wonder when you actually get to do any drawing on the screen. The short answer is that drawing happens inside of a widget's draw method when FlTk thinks the widget needs to get redrawn.
The way that most drawing happens in FlTk is that you (the application programmer) defines a new type of widget that knows how to do the drawing that you want to do. You do this by defining a new class that is a subclass of some existing type of widget and overriding the draw method.
FlTk provides a set of different functions that can go inside of a widget's draw method for drawing things like lines, images, and text. For CS559, we typically won't use these - instead relying on OpenGL to be our drawing library. A special type of widget allows OpenGL function calls to be placed into the draw method.
Some widgets, like windows, can have other windows placed inside of them.
New widgets get placed inside of the "current container." Something becomes the "current container" when it is created, or by using the begin method. So in the following code:
Fl_Window* w1 = new Fl_Window(100,100,400,200,"Window 1"); Fl_Window* w2 = new Fl_Window(500,100,400,200,"Window 2"); Fl_Slider* sl = new Fl_Value_Slider(20, 270, 250, 25); w2->end();
The slider will appear in Window 2. It is good programming practice to call end when you are done putting things inside of a container.
Making a window and putting a widget (like a button or slider) in it should not be hard. The trick is giving the widget the correct behavior when the button is pressed or the slider changes value. The way that we do this is by attaching a callback function to the widget.
A callback is a function that we attach to a widget. A widget callback function must take two arguments. The first is a pointer to the widget, and the second is a pointer whose value is stored inside the widget (called the user data).
Suppose you are writing a callback for a slider. When the slider's value changes, you want an object on the screen to change. You need some mechanism for the two to know about each other. Here are some choices (there are many others):
Style #4 is nice because it allows you to write your code in a more "object oriented style" as follows
class MySlider; void myCallback(Fl_Widget* w, void * p) { static_cast(w)->doCallback(p); } class MySlider : public Fl_Value_Slider { public: MySlider(int x, int y, int w, int h, const char* label) : Fl_Value_Slider(x,y,w,h,label) { callback(myCallback); } void doCallback(void*) { } };
Now, you can add member variables to the MySlider class so that the doCallback routine has as much data as it needs.
Some caveats:
If your callback should cause something to happen on the screen, you will need to have your widgets redraw. If you change the value of a built in widget, they will do the right thing. However, if you change your data structures and expect your widgets to notice, you have best tell them by calling their damage method.
Because FlTk callbacks pass generic pointers (Fl_Widget* for the widget pointer, and a void* for the user data), you will often need to use casts.