Example 3: Using Classes

As soon as you move away from very simple programs, you will need to organize your data and widgets, so that it is easier to create correct programs and it is easier to reuse structures you created before. This can be done using C++ classes.

This program produces an interface identical to the last one described in Example 2, but there are no static variables, the arguments to the callbacks are more consistent, and it is trivial to produce multiple examples of the windows:

// classes.cxx (example 3a)

#include <fltk/run.h>
#include <fltk/Window.h>
#include <fltk/Slider.h>
#include <fltk/Button.h>
#include <fltk/IntInput.h>
#include <stdlib.h>
using namespace fltk;

static void exit_callback(Widget *, void *) {
  exit(0);
}

class SampleWindow : public Window {
  IntInput intinput;
  Button copy_button;
  Slider slider;
  Button down_button;
  Button up_button;
  Button exit_button;

  inline void copy_callback_i() {
    slider.value(intinput.ivalue());
  }
  static void copy_callback(Widget*, void* v) {
    ((SampleWindow*)v)->copy_callback_i();
  }

  inline void down_callback_i() {
    slider.value(slider.value()-1);
    intinput.value(slider.value());
  }
  static void down_callback(Widget*, void* v) {
    ((SampleWindow*)v)->down_callback_i();
  }

  inline void up_callback_i() {
    slider.value(slider.value()+1);
    intinput.value(slider.value());
  }
  static void up_callback(Widget*, void* v) {
    ((SampleWindow*)v)->up_callback_i();
  }

  inline void slider_callback_i(Slider* slider) {
    intinput.value(slider->value());
  }
  static void slider_callback(Widget* w, void* v) {
    ((SampleWindow*)v)->slider_callback_i((Slider*)w);
  }

public:

  SampleWindow(const char* label=0) :
    Window(USEDEFAULT,USEDEFAULT,320,90,label,true),
    intinput(10,10,100,20),
    copy_button(110,10,100,20,"copy to slider"),
    slider(10,35,300,20),
    down_button(50,60,50,20,"down"),
    up_button(150,60,50,20,"up"),
    exit_button(250,60,50,20,"exit")
  {
    copy_button.callback(copy_callback,this);
    down_button.callback(down_callback,this);
    up_button.callback(up_callback,this);
    slider.callback(slider_callback,this);
    slider.set_horizontal(); slider.type(Slider::TICK_ABOVE);
    slider.range(-10,10);
    slider.step(1);
    slider.value(0);
    exit_button.callback(exit_callback);
    end();
  }

  ~SampleWindow() {}

};

int main(int argc, char ** argv) {
  SampleWindow window1("Window 1");
  SampleWindow window2("Window 2");
  window1.show(argc,argv);
  window2.show();
  return run();
}

This program displays two windows that look like this:

callbacks.gif

Let's look at the pieces of this program:

static void exit_callback(Widget *, void *) {
  exit(0);
}

This is a simple callback just like in the previous example. You can use simpler code like this when pointers to the class are not needed.

class SampleWindow : public Window {
  IntInput intinput;
  Button copy_button;
  Slider slider;
  Button down_button;
  Button up_button;
  Button exit_button;

Now we are describing our new class. It will be a window containing several widgets. These widgets are defined as instance variables inside it. The positions and labels and callbacks of these widgets will be defined below in the constructor. You can also define the widgets as pointers and have your constructor call "new" to define them, this can be more convenient in some cases and is necessary if you want to repeat or vary the set of widgets.

  inline void copy_callback_i() {
    slider.value(intinput.ivalue());
  }
  static void copy_callback(Widget*, void* v) {
    ((SampleWindow*)v)->copy_callback_i();
  }

Here is an example of a callback. It is written as two parts, the "method" copy_callback_i() which does all the work, and the static "function" copy_callback() which provides glue code that gets around C++'s limitations as to what you can put into function pointers. We recommend that you do any real work in the method, while you do any casting needed in the static function.

Because the method is inline and private, most C++ compilers will actually compile this into a single function that is as efficient as though you typed everything into the static function.

Most C++ compilers will allow the inline function and the static method to have the same name, because they have different arguments, so they both could be called copy_callback. However not all C++ compilers work with this, so we recommend the _i suffix to indicate the internal inline method.

  inline void down_callback_i() {
    slider.value(slider.value()-1);
    intinput.value(slider.value());
  }
  static void down_callback(Widget*, void* v) {
    ((SampleWindow*)v)->down_callback_i();
  }

  inline void up_callback_i() {
    slider.value(slider.value()+1);
    intinput.value(slider.value());
  }
  static void up_callback(Widget*, void* v) {
    ((SampleWindow*)v)->up_callback_i();
  }

  inline void slider_callback_i(Slider* slider) {
    intinput.value(slider->value());
  }
  static void slider_callback(Widget* w, void* v) {
    ((SampleWindow*)v)->slider_callback_i((Slider*)w);
  }

Here are more callbacks for other buttons and widgets. The slider_callback example shows how to pass an argument to the method, in this case it is a pointer to the slider. Though not necessary in this program, this is useful if there are multiple sliders that want to share the same callback code.

  SampleWindow(const char* label=0) :
    Window(USEDEFAULT,USEDEFAULT,320,90,label,true),

The constructor for our window only takes a text label for it. It first calls the base Window class. USEDEFAULT is used for the x and y positions to indicate that the window system should choose a position for the window. The last argument to Window() of true indicates that an automatic begin() should be done, so all subsequent widgets are created with this window as a parent.

    intinput(10,10,100,20),
    copy_button(110,10,100,20,"copy to slider"),
    slider(10,35,300,20),
    down_button(50,60,50,20,"down"),
    up_button(150,60,50,20,"up"),
    exit_button(250,60,50,20,"exit")

Now we have constructed all the child widgets as instance variables inside our class. The constructors automatically add these child widgets as children of our window due to the true argument passed to the window constructor.

  {
    copy_button.callback(copy_callback,this);
    down_button.callback(down_callback,this);
    up_button.callback(up_callback,this);
    slider.callback(slider_callback,this);

Inside the constructor we set the callback functions for our widgets. We actually pass a pointer to the static function and this as the user_data value. The static function will cast the void* user_data back to a pointer to this class and call the inline method.

    slider.set_horizontal(); slider.type(Slider::TICK_ABOVE);
    slider.range(-10,10);
    slider.step(1);
    slider.value(0);
    exit_button.callback(exit_callback);

Also inside the constructor we do other settings to the child widgets that cannot be done in the constructor calls to them.

    end();
  }

You must call end() on the window to set the current group new widgets are being added to back to whatever it was before.

  ~SampleWindow() {}

It is recommended you always write a destructor even if it does nothing. This is to remind you to add code here if needed later on. All fltk classes that have any virtual functions declare a virtual destructor.

int main(int argc, char ** argv) {
  SampleWindow window1("Window 1");
  SampleWindow window2("Window 2");
  window1.show(argc,argv);
  window2.show();
  return run();
}

Our main function is quite simple. It creates two instances of our window class and shows them (some people like to put the show in the constructor, so just creating the class makes the window appear). It then runs the fltk main loop. This will exit if the user closes both windows, or if they click either exit button.