GettingStartedWithFlTk

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.

Other resources:

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.

Basic Concepts:

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.

How does my program really work?

Simple Fltk programs (like we will write for CS559) usually have a structure like this:

  1. You create a number of windows (sometimes just 1) that have some number of other widgets inside of them.
  2. You tell FlTk to put these windows up on the screen (e.g. to be shown). Note that calling a widget's show method does not actually draw the widget. It simply tells FlTk that the widget should be visible, so that it will be drawn at an appropriate time. Also note that a widget is not drawn unless its parent is also shown.
  3. You have a simple loop where your program waits for an event, and then has FlTk pass that event on to the appropriate widgets.

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.

When and where do I get to draw?

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.

Placing Widgets

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 Things Happen

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):

  1. You can give your screen object a pointer to the slider. Every time the object draws itself, it will ask the slider for its value. This style is generally not recommended because it causes the program's main parts to depend on its user interface.
  2. You can use a global variable to store a pointer to the object. That way the callback function can access the global variable. Using lots of global variables is not generally considered good programming style.
  3. You can store the pointer to the object as the slider's user data. One problem with this is that you can only store one user data pointer per widget.
  4. You can make a new type of widget that is a subclass of slider. This widget will be a slider that stores extra information in it for use in its callback.

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.