Il est né ?
Vous êtes ici : www.xgarreau.org >> aide >> devel >> langtk : Initiations Langage/Toolkit graphique: C++/fltk
Version imprimable

Langage / Toolkit graphique : C++/fltk

On continue notre exploration des couples langages/toolkits graphiques. Ce mois-ci, j'ai choisi de vous présenter FLTK en collaboration avec le C++.

Présentation

FLTK (prononcez "fulltick") signifie Fast Light ToolKit. C'est une librairie disponible pour plusieurs plateformes (Linux/Windows/MacOs X). FLTK a été conçu pour être lié de façon statique avec les applications qui l'utilisent. Rappelons que celà permet de s'affranchir du contrôle des dépendances lors de la distribution d'une application sous forme binaire. Le mauvais point est que chaque application utilisant fltk est livrée avec fltk, ce qui induit une occupation d'espace disque et mémoire qui n'est pas à priori nécessaire.

Il est toutefois possible d'utiliser fltk comme un autre toolkit graphique et d'utiliser une liaison dynamique. Cette utilisation tend à se développer, fltk étant de plus en plus fourni avec les distributions linux.

Installation

Rendez vous sur la page de fltk et téléchargez la dernière version stable (1.1.5 à l'heure où vous lirez ces lignes, selon toute vraisemblance). Pour installer fltk, la méthode la plus simple consiste à décompresser le paquet, se placer dans le répertoire ainsi créé et exécuter en tant que root la commande make && make install.

C'est au cours de cette commande qu'est exécuté le script configure, vous n'êtes donc pas obligés de l'exécuter vous mêmes sauf si vous voulez des options particulières. On doit néanmoins exécuter le script configure nous mêmes pour compiler la version partagée de fltk. On tape pour ce faire ./configure --enable-shared && make && make install et les configuration, compilation et installation se déroulent sans problème.

Nous nous contenterons des versions statiques des librairies, on peut donc taper uniquement make && make install. Vous devrez être root uniquement pour être autorisé à lancer la dernière commande.

Une fenêtre

Si vous aviez suivi l'article sur gtkmm, vous n'allez pas être dépaysés par fltk. Voyons les bases en affichant une fenêtre.

#include <FL/Fl.h>
#include <FL/Fl_Window.h>

int main(int argc, char **argv) {
  Fl_Window *window = new Fl_Window(250,100, "Premier");
  window->show(argc, argv);
  return Fl::run();
}

Comme vous le voyez dans ce (très) simple programme, tout programme utilisant fltk doit inclure "FL/Fl.h" ainsi que les fichiers d'entête des classes qu'il utilise. On ne fait appel qu'à la classe Fl_Window, on inclut donc le fichier "Fl_Window.h".

Pour créer un widget, on utilise l'opérateur C++ new, comme on le ferait avec toute classe. Pour une FL_Window, les paramètres sont la largeur et la hauteur, suivies d'un titre, optionnel.

Les seconde et troisième lignes affichent la fenêtre et lance la "boucle d'évènements" fltk. Cette "boucle" redonne la main lorsque tous les widgets ont été détruits (fermez la fenêtre et le prompt revient).

Pour compiler et lier ce programme, on utilise le script fltk-config, qui s'installe en même temps que les librairies. Ce dernier renvoit, lors de son exécution, les paramètres à passer au compilateur et au linker pour mener à bien la construction du programme.

Pour obtenir les arguments à destination du compilateur, il faut utiliser fltk-config --cxxflags. Pour les options du linker, c'est fltk-config --ldflags qu'il faut saisir.

Dans le cas de notre programme simple, on peut cumuler les deux en une seule commande
g++ -o cpp_fltk_0 cpp_fltk_0.cxx `fltk-config --ldflags --cxxflags`
Ce qui est quand même plus simple à taper que l'équivalent, généré par fltk-config :
g++ -o cpp_fltk_0 cpp_fltk_0.cxx -I/usr/local/include -I/usr/X11R6/include -L/usr/local/lib -L/usr/X11R6/lib -lfltk -lm -lXext -lX11 -lsupc++

C++/FLTK scshot

Des widgets

Bien, maintenant, qu'on a réussi à faire une fenêtre, on va essayer de la remplir.

Dans fltk, vous ajoutez des widgets au "conteneur en cours". Par défaut, un conteneur est "en cours" depuis sa création jusqu'à l'appel de sa méthode end() ou la création d'un autre conteneur. Les "groupes" sont les conteneurs de widgets en fltk. On y trouve bien sûr les fenêtres, les Fl_Window. Ceci pour vous expliquer qu'en fltk, il n'utilise rarement des méthodes comme add ou pack* comme c'est souvent le cas avec les autres toolkits graphiques. fltk ajoute tout seul le widget construit au groupe courant. On crée les widgets grâce à l'opérateur new tout simplement.

Pour notre application habituelle, il nous faut un widget capable d'afficher une ligne de texte, une zone de texte éditable et un bouton. On choisit respectivement un Fl_Box, approprié pour des textes statiques ou quasi-statiques, un Fl_Input, zone de texte éditable enfin un Fl_Button qui se passe de commentaires.

Notre problème va être le widget Fl_Box. Ce dernier, prévu pour des textes statiques, ne copie pas la chaîne qu'on lui attribue mais seulement son adresse. La chaîne doit donc rester valide dans le temps. On contourne ce problème en ajoutant une variable chargée de stocker le contenu de la Fl_Box. On pourrait aussi utiliser une FL_Output, qui est un Fl_Input non éditable mais ce n'est pas la solution que j'ai choisie. Il y a plusieurs façons de faire, choisissez l'autre si vous le préférez.

#include <FL/Fl.H>

#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Button.H>
#include <string>

std::string sortie_val;

int main(int argc, char **argv) {
  Fl_Window *fen = new Fl_Window(250, 100, "Bonjour");
  Fl_Box *sortie = new Fl_Box(10,10,230,30);
  Fl_Input *entree = new Fl_Input(10,40,230,30);
  Fl_Button *bouton = new Fl_Button(10,70,230,30, "Bonjour !");

  sortie_val = "Bonjour Monde !";
  sortie->label(sortie_val.c_str());

  fen->end();
  fen->show(argc, argv);
  return Fl::run();
}

Comme je le disais plus haut, on retrouve les entêtes nécessaires, une par widget et l'entête commune Fl.H. On utilise aussi des chaînes de la librairie standard C++, d'où le #include <string>.

On trouve ensuite la variable globale sortie_val, qui nous servira de zone mémoire pour le widget Fl_Box. On aurait pu également réduire sa portée au main mais ce n'est pas indispensable ici. Quoiqu'il en soit, nous la déclarerons en tant que membre de la classe fenêtre dans l'exemple fini.

Le main commence par la création des widgets, ces derniers sont ajoutés dans l'ordre au conteneur en cours, c'est à dire, pour les trois derniers widgets, l'objet Fl_Window, cette dernière étant gérée par fltk.

Pour construire une fenêtre, on passe en arguments au constructeur sa largeur, sa hauteur et son titre. Pour les widgets, les paramètres sont les positions en x et y, la largeur, la hauteur et un label, optionnel. Ce label est placé dans le widget dans le cas de Fl_Button et Fl_Box et à gauche dans le cas du Fl_Input, comme titre du champ.

On initialise la string sortie_val, sans se soucier de la mémoire donc ... En revanche, c'est un pointeur de type char* qu'il nous faut passer à la méthode label pour affecter la chaîne de caractères au widget Fl_Box. On utilise donc la méthode string::c_str() pour obtenir le pointeur vers la zone mémoire où sont stockés les caractères de notre chaîne. Attention toutefois ! Vous devez refaire cette affectation après chaque modification de votre string, l'emplacement en mémoire étant susceptible de changer lors de cette opération.

On "termine" ensuite l'édition de la fenêtre (end()) et on la montre (show). Il ne nous reste plus alors qu'à admirer notre oeuvre en attendant de fermer la fenêtre.

Des callbacks

Oui, car si on obtient bien l'interface voulue, elle ne réagit pour l'instant à aucune action. On peut saisir du texte et appuyer sur le bouton mais pour notre programme, ces actions ne correspondent à rien. C'est ici qu'interviennent les callbacks. Si vous avez déjà utilisé un toolkit graphique quel qu'il soit et quel que soit le système d'exploitation sur lequel vous l'avez rencontré, vous connaissez la notion de callbacks. Bonne nouvelle, avec fltk, c'est simplissime ! On déclare un callback pour un widget fltk grâce à la méthode callback de prototype void Fl_Widget::callback(Fl_Callback*, void* = 0). Cette méthode prend en argument une fonction callback et un pointeur vers "des données". Les callbacks, de type (Fl_Callback), doivent avoir le prototype suivant : void mon_callback (Fl_Widget*, void*). Lors de l'appel du callback, ce dernier reçoit le widget responsable du callback et un pointeur vers "des données", le même que celui passé en paramètre à la méthode callback.

En fltk, chaque widget ne peut avoir qu'un seul callback. On définit quand ce dernier est appelé grâce à la méthode void Fl_Widget::when(Fl_When). Fl_When est une énumération représentant les évènements pouvant survenir. Pour les Fl_Button, on peut déclencher un calback lors des évènements de "changement d'état" (FL_WHEN_CHANGED) et de clic de bouton (FL_WHEN_RELEASE). Par défaut, le callback est utilisé lors du clic, ce qui nous convient. Nous n'auront donc pas à utiliser la méthode when.

Notre callback devra, lors d'un clic sur le bouton, prendre la valeur entrée par l'utilisateur dans le Fl_Input et construire une chaîne à afficher dans la Fl_Box. Nous disposons de la possibilité de ne trasmettre qu'un seul paramètre, on doit donc regrouper les deux dans une structure.

typedef struct _cb_data_type {
	Fl_Input* input;
	Fl_Box* box;
} cb_data_type;

Le code de "configuration" du callback doit donc initialiser une structure de type cb_data_type avec des pointeurs vers les widgets correspondants de notre interface.

  cb_data_type cb_data;
  cb_data.input = entree;
  cb_data.box = sortie;
  bouton->callback(mon_callback, &cb_data);

Enfin, le travail de notre callback est d'interpréter le pointeur vers des données de type cb_data_type, d'extraire la valeur saisie par l'utilisateur et d'afficher un message de salutation dans la Fl_Box.

void mon_callback(Fl_Widget* w, void* ptr) {
	cb_data_type* cb_data = static_cast(ptr);
	sortie_val = "Bonjour ";
	sortie_val += cb_data->input->value();
	sortie_val +=  " !";
	cb_data->box->label(sortie_val.c_str());
}

Ce qui nous fait arriver au code suivant :

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>

#include <FL/Fl_Button.H>
#include <string>

std::string sortie_val;

typedef struct _cb_data_type {
	Fl_Input* input;
	Fl_Box* box;
} cb_data_type;

void mon_callback(Fl_Widget* w, void* ptr) {
	cb_data_type* cb_data = static_cast<cb_data_type*>(ptr);
	sortie_val = "Bonjour ";
	sortie_val += cb_data->input->value();
	sortie_val +=  " !";
	cb_data->box->label(sortie_val.c_str());
}

int main(int argc, char **argv) {
  Fl_Window *fen = new Fl_Window(250, 100, "Bonjour");
  Fl_Box *sortie = new Fl_Box(10,10,230,30);
  Fl_Input *entree = new Fl_Input(10,40,230,30);
  Fl_Button *bouton = new Fl_Button(10,70,230,30, "Bonjour !");

  sortie_val = "Bonjour Monde !";
  sortie->label(sortie_val.c_str());

  cb_data_type cb_data;
  cb_data.input = entree;
  cb_data.box = sortie;
  bouton->callback(mon_callback, &cb_data);

  fen->end();
  fen->show(argc, argv);
  return Fl::run();
}

En compilant celà (g++ -o cpp_fltk_0 cpp_fltk_0.cxx `fltk-config --ldflags --cxxflags`), on obtient une application qui fonctionne ...

C++/FLTK scshot

Programmation objet

N'importe qui ayant eu une scéance de travaux dirigés de programmation objet, crierait au scandale en voyant un tel code. Notre fenêtre pourrait et devrait être une "boîte noire" pour l'application. Nous allons donc transformer ce code en quelque chose de plus orienté objet.

Tout d'abord créons un objet pour notre fenêtre. Cet objet hérite d'une Fl_Window.

Ses membres sont logiquement composés des différents widgets que la fenêtre embarque. Tous ces widgets sont privés.

Le callback doit bien évidemment être une méthode de cette classe, pour pouvoir accéder aux widgets, privés. Le problème qui se pose est qu'on ne peut pas passer en premier paramètre de la méthode callback une méthode de la classe, à moins que celle ci ne soit statique. D'un autre côté, dans une méthode statique, on n'a pas accès aux membres d'instance, seulement aux membres statiques. Pour contourner ce problème, on utilise le deuxième paramètre pour passer un pointeur vers l'instance de classe contenant le bouton (this). Cela semble compliqué mais simplifie largement le problème par rapport à la méthode du "tout global" utilisée précédemment.

L'appel de la méthode callback sera donc :

bouton->callback(static_bouton_callback, this);

static_bouton_callback étant une méthode privée statique. Pour pouvoir consulter et modifier les valeurs des widgets, on utilise une méthode d'instance, bouton_callback, que l'on appelle via le pointeur this passé en paramètre.

void ZazouFenetre::static_bouton_callback(Fl_Widget* w, void* ptr) {
	ZazouFenetre* me = static_cast<ZazouFenetre*>(ptr);
	me->bouton_callback();
}

Ce point délicat étant réglé, voici le code du programme entier :

#include <FL/Fl.H>
#include <FL/Fl_Window.H>

#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Button.H>
#include <string>

class ZazouFenetre : private Fl_Window
{
private:
	std::string sortie_val;
	Fl_Box *sortie;
	Fl_Input *entree;
	Fl_Button *bouton;
	void bouton_callback();
	static void static_bouton_callback(Fl_Widget* w, void* ptr);

public:
	ZazouFenetre();
	virtual ~ZazouFenetre() {};
};

void ZazouFenetre::bouton_callback() {
	sortie_val = "Bonjour ";
	sortie_val += entree->value();
	sortie_val +=  " !";
	sortie->label(sortie_val.c_str());
}

void ZazouFenetre::static_bouton_callback(Fl_Widget* w, void* ptr) {
	ZazouFenetre* me = static_cast<ZazouFenetre*>(ptr);
	me->bouton_callback();
}

ZazouFenetre::ZazouFenetre() : Fl_Window(250, 100, "Bonjour") {
  sortie = new Fl_Box(10,10,230,30);
  entree = new Fl_Input(10,40,230,30);
  bouton = new Fl_Button(10,70,230,30, "Bonjour !");

  sortie_val = "Bonjour Monde !";
  sortie->label(sortie_val.c_str());

  bouton->callback(static_bouton_callback, this);

  end();
  show();
}


int main(int argc, char **argv) {
  ZazouFenetre fen;
  return Fl::run();
}

Vu l'utilisation que l'on fait de la ZazouFenetre, on choisit un héritage privé de Fl_Window. A des fins de réutilisabilité et d'extension, on choisirait toutefois vraisemblablement plutôt un héritage public.

On retrouve ensuite les widgets et la std::string stockant le label de la Fl_Box, le message "Bonjour toi !", le callback statique et la méthode appelée par ce dernier.

Le constructeur se charge de la création des widgets et de l'affectation du callback puis affiche la fenêtre

Le destructeur ne fait rien, on n'a pas à se charger de la destruction des widgets.

Le programme ainsi écrit ne fait rien de plus, ne le fait pas mieux mais cette version donne théoriquement plus le sentiment du travail bien fait à son développeur.

Nous voilà arrivés au terme de cet article. Je me doute qu'il ne fera pas grimper fltk au premier rang des toolkits graphiques les plus utilisés mais vous avez à présent une piste de plus à fouiller pour le cas où vous souhaiteriez distribuer une application indépendante de tout toolkit préinstallé chez vos "clients".

@+

Xavier Garreau - <xavier@xgarreau.org>
http://www.xgarreau.org/

Liens & Références :

a+

Auteur : Xavier GARREAU
Modifié le 02.08.2008

Rechercher :

Google
 
Web www.xgarreau.org