I. Introduction

Nous allons voir comment créer une interface graphique à l'aide du langage Ruby, et de la bibliothèque GTK (Gimp Toolkit). Pourquoi ces choix ? GTK est une bibliothèque moderne, permettant de créer des interfaces graphiques belles et efficaces, et conçue pour la programmation objet. D'autre part, Ruby est un langage objet puissant et simple d'utilisation, trop méconnu, qui permet de développer rapidement des applications complexes.

Nous supposerons une bonne connaissance de Ruby, mais aucune connaissance particulière de GTK. Toutefois, si vous avez déjà utilisé une bibliothèque graphique basée sur des évènements (c'est le cas de SWING, par exemple), vous ne devriez pas être surpris. Nous essayerons d'aller du général au particulier, en revenant plusieurs fois sur les mêmes sujets pour les approfondir, aussi ne vous étonnez pas si nous passons sous silence certains sujets au début.

I-A. Mise en place

Pour pouvoir créer des interfaces graphiques en Ruby/GTK, vous aurez besoin de Ruby, bien sûr, et de la bibliothèque Ruby/GTK. Cet article décrit l'utilisation de la version 2 de GTK, mais est facilement adaptable à la version 1.

Une fois ceci installé vous pourrez utiliser GTK en rajoutant la ligne suivante dans vos programmes.

 
Sélectionnez
require 'gtk2'

I-B. Où trouver plus d'informations ?

Vous avez, tout d'abord, la page officielle de Ruby/GTK, en anglais, qui contient beaucoup de documentation. Vous pouvez aussi vous baser sur la documentation de GTK qui, bien qu'écrite pour le langage C, est facilement transposable.

Par exemple, la fonction gtk_entry_set_text(entry, "blah") s'écrira en Ruby entry.text = "blah" ou entry.set_text("blah"). Et la fonction gtk_button_new_with_label("Toto") s'écrira Gtk::Button.new("Toto").

II. Concepts de base

Une interface graphique est composée de widgets (éléments graphiques) placés dans des conteneurs, qui indiquent comment placer ces widgets à l'écran (un conteneur est aussi un widget). Le premier conteneur que vous allez rencontrer est Gtk::Window, qui représente une fenêtre, et peut contenir un seul élément. Nous allons voir immédiatement un premier exemple très simple :

 
Sélectionnez
require 'gtk2'

Gtk.init

window = Gtk::Window.new
button = Gtk::Button.new('Bonjour tout le monde')
window.add(button)

button.show
window.show

Gtk.main

Tout d'abord, Gtk.init sert à initialiser la bibliothèque GTK, et vous ne devez pas créer d'objets de cette bibliothèque avant d'avoir appelé cette méthode. Nous ne décrirons pas ce qu'elle fait en détails, souvenez-vous simplement de l'appeler. Ensuite, nous créons une fenêtre, ainsi qu'un bouton auquel nous attribuons le texte "Bonjour tout le monde", et nous ajoutons le bouton dans la fenêtre. Puis, nous rendons ce bouton et cette fenêtre visibles. Par défaut, les widgets ne sont pas visibles, et il faudra donc ne pas oublier de les rendre visible. Enfin, nous appelons Gtk.main, qui lance la "boucle principale" du programme (nous allons expliquer cela tout de suite).

Maintenant, exécutez ce programme. Vous devriez voir s'afficher une fenêtre contenant uniquement le bouton que nous avons décrit. Cette fenêtre vous offre les fonctionnalités habituelles : vous pouvez la redimensionner, la déplacer, et utiliser les boutons standards définis par votre gestionnaire de fenêtres (ie. réduire, agrandire, fermer). Vous pouvez également cliquer sur le bouton, mais il ne se passera rien. De plus, si vous fermez la fenêtre, vous pourrez constater que le programme ne se termine pas pour autant. Cela nous amène à la gestion des évènements.

II-A. Les évènements (signaux)

Lorsque nous créons un bouton, nous voudrions pouvoir dire "quand l'utilisateur clique sur le bouton, afficher Bonjour". De même, nous voudrions pouvoir indiquer à la fenêtre "quand l'utilisateur fermer cette fenêtre, terminer le programme". Avec GTK, cela se fait en associant une méthode à un signal (ne pas confondre avec les signaux UNIX, bien que le concept soit similaire). Chaque widget dispose d'une panoplie de signaux auquel il peut réagir. Par exemple, un bouton peut réagir au signal "clicked", lorsque l'utilisateur a cliqué sur ce bouton :

 
Sélectionnez
button = Gtk::Button.new('Bonjour')
button.signal_connect('clicked') {
   print "Bonjour tout le monde\n"
}

Nous parlions plus haut de la "boucle principale" du programme. Nous pouvons maintenant vous expliquer de quoi il s'agit : un programme graphique est différent d'un programme "classique", car il ne possède pas un début et une fin prédéterminées. Sa tâche est d'attendre des actions de l'utilisateur. Une fois l'interface graphique construite, il faut donc se placer dans une boucle, dans laquelle le programme attend indéfiniment que des évènements se produisent, et appelle les méthodes correspondant à ces évènements. Et ce, jusqu'à ce qu'un évènement indique la fin du programme.

Voyons une version améliorée de notre premier exemple :

 
Sélectionnez
require 'gtk2'

Gtk.init

window = Gtk::Window.new
window.signal_connect('destroy') {
   Gtk.main_quit
}

button = Gtk::Button.new('Bonjour tout le monde')
button.signal_connect('clicked') {
   print "Bonjour !\n"
}
window.add(button)

window.show_all

Gtk.main

print "Terminé\n"

Dans cet exemple, nous avons connecté la méthode Gtk.main_quit au signal indiquant que l'on demande la destruction de la fenêtre. Cette méthode permet de sortir de la boucle principale, et de poursuivre le programme. Ainsi, lorsque nous fermons la fenêtre, le programme continue de s'exécuter après la ligne Gtk.main, et affiche "Terminé".

Autre léger changement, nous avons remplacé les lignes :

 
Sélectionnez
button.show
window.show

Par un raccourcis qui rend visible un widget et tous les widgets qui sont contenus dans celui-ci, récursivement :

 
Sélectionnez
window.show_all

II-B. Les conteneurs

Pour le moment, nous n'avons vu que la fenêtre, qui est un conteneur ne pouvant contenir qu'un seul widget. Il s'agissait du conteneur Gtk::Bin, dont Gtk::Window hérite. Mais ceci n'est pas suffisant pour faire une interface graphique, nous allons donc voir les autres conteneurs disponibles.

II-B-1. Les boîtes

Les boîtes sont un type de conteneur très simple, semblables à n'importe quel carton dans lequel on jette des revues : elles s'empilent les unes au dessus des autres, dans l'ordre dans lequel on les a jetées. Il existe des boîtes verticales (Gtk::VBox) et des boîtes horizontales (Gtk::HBox), qui héritent toutes les deux de l'objet générique Gtk::Box. Lorsque l'on crée une boîte, on indique si elle est homogène, c'est à dire si tous les éléments à l'intérieur ont droit au même espace, et on indique aussi l'espace vide laissé entre deux éléments. Une fois la boîte créée, on peut lui ajouter des éléments au début (pack_start) ou à la fin (pack_end), et c'est tout ce que vous avez besoin de savoir !

Voyons quelques exemples de boîtes

 
Sélectionnez
window = Gtk::Window.new
window.set_title('VBox homogene')
window.signal_connect('destroy') { Gtk.main_quit }

vbox = Gtk::VBox.new(true, 6)
vbox.pack_start(Gtk::Button.new('Un'))
vbox.pack_start(Gtk::Button.new('Deux'))
vbox.pack_start(Gtk::Button.new('Trois'))
window.add(vbox)

window.show_all

Le résultat devrait ressembler à cela :

Image non disponible

En réalité, les méthodes pack_start et pack_end peuvent prendre plus de paramètres. Ces paramètres sont, dans l'ordre, expand, fill et padding. Pour expliquer ces paramètres, nous devons préciser un peu le fonctionnement des boîtes dans GTK. Un widget placé dans un conteneur demande une certaine quantité d'espace, et est alloué une certaine quantité d'espace, qui peut être différente de la quantité demandée. Par exemple, lorsque l'on agrandit la fenêtre, les widgets se voient allouer un espace peut-être plus grand que celui qui était strictement nécessaire. Voyons comment cet espace sera utilisé.

Tout d'abord, si la boîte était homogène, l'espace dont elle dispose est réparti équitablement entre tous les widgets qu'elle contient. Si une boîte verticale dispose de 450 pixels et contient trois boutons, chaque bouton se verra attribuer 150 pixels, moins la valeur du padding, qui est un espace vide laissé entre chaque widget. Il y a alors deux cas possibles : si le champ fill est à true, alors le widget occupera l'intégralité de cet espace, même s'il n'en avait pas besoin. Si le champ fill est à false, alors le widget n'occupera que l'espace nécessaire, l'espace supplémentaire étant laissé vide.

D'autre part, si la boîte n'est pas homogène, alors le paramètre expand entre en jeu. Ce paramètre indique si le widget désire se voir attribuer une partie de l'espace supplémentaire ou non. Si une boîte dispose de 150 pixels "en plus", et contient trois boutons, dont deux ont le champ expand à true, alors chacun d'entre eux se verra attribuer 75 pixels (moins le padding). Si les trois boutons ont expand à true, ils se voient attribuer chacun 50 pixels (moins le padding). Enfin, le champ fill est toujours applicable, et indique si l'espace "en plus" est utilisé par le widget, ou laissé vide. Voyons un exemple

 
Sélectionnez
window = Gtk::Window.new
window.set_title('VBox homogene')
window.signal_connect('destroy') { Gtk.main_quit }

vbox = Gtk::VBox.new(true, 6)
vbox.pack_start(Gtk::Button.new('Un'), false)
vbox.pack_start(Gtk::Button.new('Deux'), true, false)
vbox.pack_start(Gtk::Button.new('Trois'), true, true)

window.add(vbox)

window.show_all
Image non disponible

Dans cet exemple, nous pouvons voir que le premier bouton ne s'étend pas, il n'obtient donc que l'espace nécessaire, même si nous agrandissons la fenêtre. Le deuxième bouton s'étend, mais ne remplit pas l'espace en plus, qui est donc laissé vide. Notez que les deux "vides" sur l'image appartiennent au deuxième bouton. Enfin, le troisième bouton s'étend et remplit l'espace en plus, ce qui est le comportement par défaut.

Enfin, le dernier paramètre, le padding, peut-être spécifié à la suite des autres attributs, et indique l'espace vide qui doit être placé avant et après le widget :

 
Sélectionnez
vbox.pack_start(Gtk::Button.new('Trois'), true, true, 12)

Bien sûr, tous les exemples que nous avons donnés sont avalables pour les boîtes horizontales. Vous pouvez les essayer en ne changeant que la création de la boîte, en remplaçant VBox par HBox. Vous constaterez au passage que le paramètre fill n'affecte que la dimension de la boîte : la hauteur pour une boîte verticale, la largeur pour une boîte horizontale.

II-B-2. Les tables

Une table (Gtk::Table) est un conteneur un peu plus complexe qu'une boîte, très semblable aux tableaux en HTML. Elle se définit par son nombre de rangées, son nombre de colonnes, et le fait qu'elle soit homogène ou non. Si elle est homogène, toutes ses cases ont la même taille. Une fois votre table créée, vous pouvez y déposer (attacher) des widgets, en indiquant dans quelle(s) case(s), vous voulez les mettre. Un widget peut s'étaler sur plusieurs cases, mais une case ne peut contenir qu'un seul widget au maximum.

Lorsque vous voulez attacher un widget dans une table, vous commencez par spécifier la colonne de départ (gauche), puis la colonne de fin (droite), la rangée de départ (haut) et la rangée de fin (bas). Voyons un exemple.

 
Sélectionnez
table = Gtk::Table.new(4,5)
b1 = Gtk::Button.new('Un')
table.attach(b1, 0, 2, 0, 2)
b2 = Gtk::Button.new('Deux')
table.attach(b2, 3, 4, 0, 1)
b3 = Gtk::Button.new('Trois')
table.attach(b3, 3, 4, 1, 2)
b4 = Gtk::Button.new('Quatre')
table.attach(b4, 0, 4, 2, 3)

window.add(table)
Image non disponible

Il est bien sûr possible de choisir si les widgets s'étendent et remplissent tout l'espace alloué, ainsi que spécifier un padding. Pour cela, la méthode attach prend des paramètres supplémentaires, qui sont les suivants :

  • xoptions, yoptions : peuvent être Gtk::EXPAND, Gtk::FILL ou Gtk::SHRINK, ou une combinaison de ces paramètres ( Gtk::EXPAND | Gtk::FILL). Spécifient les options pour l'agencement horizontal (xoptions) et vertical (yoptions). Les attributs EXPAND et FILL ont la même signification que ci-dessus, et SHRINK indique que le widget doit prendre le minimum d'espace possible.
  • xpadding, ypadding : valeurs de padding horizontal et vertical pour le widget attaché.
 
Sélectionnez
table = Gtk::Table.new(4,5)
b1 = Gtk::Button.new('Un')
table.attach(b1, 0, 2, 0, 2, Gtk::SHRINK)
b2 = Gtk::Button.new('Deux')
table.attach(b2, 3, 4, 0, 1, Gtk::EXPAND)
b3 = Gtk::Button.new('Trois')
table.attach(b3, 3, 4, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK)
b4 = Gtk::Button.new('Quatre')
table.attach(b4, 0, 4, 2, 3)
Image non disponible

III. Survol des widgets de base

III-A. Boutons

III-A-1. Gtk::Button

Nous avons déjà vu l'utilisation de base de ce widget, sous la forme d'un bouton contenant du texte. Cependant, un bouton peut contenir n'importe quel widget. On peut le créer en passant du texte à son constructeur, auquel cas un Label est créé, mais si on laisse le constructeur vide, on peut ensuite ajouter le widget de son choix (un Button hérite de Bin, un conteneur). La plupart du temps, cela est utilisé pour mettre une image au lieu d'un texte.

Nous avons vu le signal clicked, émis lorsque l'utilisateur clique sur le bouton (ou l'active avec le clavier). Il existe d'autres signaux :

enter leave pressed released activate
Émis lorsque la souris arrive sur le bouton Émis lorsque la souris sort du bouton Émis lorsque l'utilisateur appuie sur un bouton de la souris Émis lorsque l'utilisateur relâche un bouton de la souris Émis lorsque le bouton est activé autrement qu'en cliquant dessus (touche espace ou entrée alors qu'il détient le focus, par exemple)

Vous pouvez également modifier l'apparence de relief de votre bouton. Cela est utile en particulier si vous faites une barre d'outils contenant de nombreux boutons, et que vous désirer supprimer les bords de ces boutons. Pour cela, vous pouvez utiliser la méthode set_relief ou relief= en lui passant une valeur parmi Gtk::RELIEF_NONE, Gtk::RELIEF_HALF et Gtk::RELIEF_NORMAL.

Enfin, il est possible de créer un bouton en utilisant des modèles prédéfinis. Depuis sa version 2, GTK est livré avec un stock de boutons prédéfinis, qui vous évitent d'avoir à recréer des boutons toujours identiques et à vous soucier des normes de nommage et de traduction. Nous reviendrons sur ce sujet ultérieurement, mais voyons un exemple :

 
Sélectionnez
window = Gtk::Window.new
window.set_title('Boutons predefinis')
window.signal_connect('destroy') { Gtk.main_quit }

hb = Gtk::HBox.new(false, 6)
b = Gtk::Button.new(Gtk::Stock::OK)
hb.pack_start(b)
b = Gtk::Button.new(Gtk::Stock::CANCEL)
hb.pack_start(b)

window.add(hb)
window.show_all
Image non disponible

III-A-2. Gtk::ToggleButton

Un ToggleButton est un Button qui reste enfoncé lorsque l'on clique dessus. Cliquer une seconde fois le remet dans sa position initiale. Il s'agit, par exemple, des widgets utilises pour la barre d'outils de The Gimp. La création d'un ToggleButton se fait de la même façon que celle d'un bouton normal.

Vous pouvez obtenir ou changer l'état d'un ToggleButton à l'aide des méthodes active? et active=, comme nous allons le voir dans l'exemple ci-dessous. La valeur du champ 'active' est un booléen, vrai si le bouton est activé, faux sinon. Cependant, il existe un autre niveau intermédiaire, représentant le cas 'inconsistant'. Par exemple, si vous avez un bouton représentant le fait qu'un texte soit souligné ou non, et que vous sélectionnez du texte dont une partie seulement est soulignée, alors vous ne pouvez mettre le bouton ni actif ni inactif.

 
Sélectionnez
hb = Gtk::HBox.new(false, 6)
b = Gtk::ToggleButton.new('Inactif')
hb.pack_start(b)
b = Gtk::ToggleButton.new('Actif')
b.active=true
hb.pack_start(b)
b = Gtk::ToggleButton.new('Inconsistant')
hb.pack_start(b)
b.inconsistent=true

window.add(hb)
window.show_all
Image non disponible

Enfin, ces boutons disposent d'un signal nommé toggled, qui est envoyé lorsque l'état du bouton change (lorsqu'il devient activé ou désactivé). Pour l'utiliser, vous aurez probablement besoin de la valeur actuelle du bouton, et pas simplement de savoir qu'elle a changé. Ceci nous amène à voir les paramètres qui sont passés au bloc que nous associons à un signal. En règle général, ces paramètres sont le widget qui est à l'origine du signal, et des données utilisateur (nous verrons plus tard comment changer ces données). Voyons un exemple :

 
Sélectionnez
b = Gtk::ToggleButton.new('Inactif')
b.signal_connect('toggled') { |widget, data|
        print widget.active?.to_s + "\n"
}

Lorsque vous cliquerez sur le bouton, son état actuel s'affichera dans le terminal.

III-A-3. Gtk::CheckButton

Ces widgets sont connus en français sous le nom de "cases à cocher". Ils héritent directement de Gtk::ToggleButton et se comportent de la même manière. La seule chose qui diffère est leur représentation graphique : ils sont consitués d'une cases à cocher suivie d'un autre widget, le plus souvent un label.

 
Sélectionnez
hb = Gtk::HBox.new(false, 6)
b = Gtk::CheckButton.new('Inactif')
hb.pack_start(b)
b = Gtk::CheckButton.new('Actif')
b.active=true
hb.pack_start(b)
b = Gtk::CheckButton.new('Inconsistant')
hb.pack_start(b)
b.inconsistent=true
Image non disponible

III-A-4. Gtk::RadioButton

Ces boutons ressemblent beaucoup aux cases à cocher, mais sont un peu plus compliqués à utiliser. Il s'agit également de widgets possédant deux états (plus l'état inconsistant), mais il est possible de les réunir en groupes, et au sein d'un même groupe, seul un d'entre eux peut être actif à la fois (même s'il est possible qu'aucun ne soit actif). Cela signifie que lorsqu'on active un bouton radio, tous les autres boutons du même groupe deviennent inactifs.

En pratique, on commence par créer le premier bouton sans spécifier de groupe, puis on crée les suivants à partir du premier, en le leur passant comme premier paramètre. Il est aussi possible d'obtenir (ou de changer) le groupe d'un bouton avec les méthodes group et group=.

 
Sélectionnez
vb = Gtk::VBox.new(false, 6)
b = Gtk::RadioButton.new('Bonbon')
vb.pack_start(b)
b2 = Gtk::RadioButton.new(b, 'Caramel')
vb.pack_start(b2)
b2 = Gtk::RadioButton.new(b, 'Inconsistant')
vb.pack_start(b2)
b2.inconsistent = true
Image non disponible

III-B. Entrée de données

III-B-1. Gtk::Entry

Ce widget représente une boîte de saisie de texte, d'une seule ligne. Il dispose de nombreuses méthodes, mais les plus utiles sont text et text=, pour obtenir et changer le texte contenu dans la boîte. Voyons un exemple qui sera plus clair que n'importe quelle explication :

 
Sélectionnez
vb = Gtk::VBox.new(true, 6)

hb = Gtk::HBox.new(false, 6)
hb.pack_start(Gtk::Label.new('Nom'), false, true, 6)
nom = Gtk::Entry.new
hb.pack_start(nom, true, true)
vb.pack_start(hb)

hb = Gtk::HBox.new(false, 6)
hb.pack_start(Gtk::Label.new('Mot de passe'), false, true, 6)
pass = Gtk::Entry.new
pass.visibility = false
hb.pack_start(pass, true, true)
vb.pack_start(hb)

window.add(vb)
Image non disponible

Citons quelques exemples des possibilités qui s'offrent à nous. Il est possible de donner une taille maximale au texte entré dans la boîte avec max_length=, d'empêcher l'utilisateur de taper du texte avec editable = false, ou de changer le caractère de masquage (par défaut, une étoile) avec invisible_char=. Notons que cette dernière méthode demande un entier, il nous faut donc l'utiliser comme cela, si nous voulons un "@" comme caractère de masquage :

 
Sélectionnez
pass.visibility = false
pass.invisible_char= '@'[0]

Enfin, parmi les signaux proposés par une boîte de texte, nous pouvons trouver activate, qui est émis lorsque l'on valide la boîte de texte (le plus souvent en appuyant sur entrée).

III-C. Boîtes de message

Gtk possède des classes décrivant les fenêtres standard (Gtk::Window) ou les boîtes de dialogue (Gtk::Dialog), qui permettent de créer tout ce que vous voulez. Mais si vous avez juste besoin d'afficher un message à l'utilisateur, il existe un moyen plus rapide et plus simple que de créer votre propre fenêtre. Il s'agit du widget Gtk::MessageDialog.

Ce widget représente une fenêtre de dialogue avec un message, une icône, et des boutons standards. Il existe quatre type des dialogues prédéfinis : information, avertissement, question et erreur. Il existe aussi six types de boutons prédéfinis : aucun, ok, fermer, annuler, Oui/Non et Ok/Annuler. Enfin, vous pouvez choisir que la fenêtre soit modale (c'est à dire qu'elle bloque l'application, et que l'utilisateur doit répondre au message avant de continuer à travailler) ou non. Voyons un exemple :

 
Sélectionnez
vb = Gtk::VBox.new(true, 6)

b = Gtk::Button.new('Ouvrir le dialogue')
b.signal_connect('clicked') {
  d = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT,
                             Gtk::MessageDialog::INFO,
                             Gtk::MessageDialog::BUTTONS_CLOSE,
                             "Bonjour tout le monde")
  d.run
  d.destroy
}

vb.pack_start(b)
Image non disponible

Si on utilise la méthode run du dialogue, il sera modal, et se terminera lorsque l'utilisateur cliquera sur un bouton. La méthode destroy est ensuite appelée pour fermer la fenêtre. Si vous désirez procéder autrement, par exemple si vous ne voulez pas une fenêtre modale, alors vous devrez attribuer un bloc au signal "response". Référez-vous à la documentation de la super-classe Gtk::Dialog concernant ce signal, et la manière de savoir quel bouton a été cliqué. Nous détaillerons cela ultérieurement.

Les types de message prédéfinis dont nous avons parlé sont INFO, WARNING, QUESTION et ERROR. Pour les boutons, vous avez le choix entre NONE, OK, CLOSE, CANCEL, YES_NO et OK_CANCEL. Enfin, le deuxième paramètre du constructeur contient des attributs spécifiques à la classe Dialog, que nous ne détaillerons pas car ils ne sont pas très utiles à ce stade.

IV. Exemple concret : un convertisseur de devises

Voici le code, d'un seul bloc. Vous pouvez l'exécuter directement. Normalement, vous devriez pouvoir le comprendre facilement, mais nous allons l'expliquer brièvement.

 
Sélectionnez
require 'gtk2'

taux = 6.55957

Gtk.init
window = Gtk::Window.new
window.set_title('Convertisseur')
window.signal_connect('destroy') { Gtk.main_quit }

# Une boîte verticale qui contiendra trois rangées: Francs,
# Euros, et bouton Fermer.
vb = Gtk::VBox.new(true, 6)

# Partie "Francs"
hb = Gtk::HBox.new(false, 6)
hb.pack_start(Gtk::Label.new('Francs'), false, true, 6)
frf = Gtk::Entry.new
frf.signal_connect('activate') {
  e = frf.text.to_f / taux
  d = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT,
                             Gtk::MessageDialog::INFO,
                             Gtk::MessageDialog::BUTTONS_CLOSE,
                             "#{frf.text.to_f} FRF = #{e} EUR")
  d.run
  d.destroy
}
hb.pack_start(frf, true, true)
b = Gtk::Button.new('-> EUR')
# Quand on clique sur le bouton, on active la boîte de texte, ce qui
# déclenchera le signal 'activate'.
b.signal_connect('clicked') { frf.activate }
hb.pack_start(b)
vb.pack_start(hb)

# Boîte "Euros"
hb = Gtk::HBox.new(false, 6)
hb.pack_start(Gtk::Label.new('Euros'), false, true, 6)
eur = Gtk::Entry.new
eur.signal_connect('activate') {
  f = eur.text.to_f * taux
  d = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT,
                             Gtk::MessageDialog::INFO,
                             Gtk::MessageDialog::BUTTONS_CLOSE,
                             "#{eur.text.to_f} EUR = #{f} FRF")
  d.run
  d.destroy
}
hb.pack_start(eur, true, true)
b = Gtk::Button.new('-> FRF')
b.signal_connect('clicked') { eur.activate }
hb.pack_start(b)
vb.pack_start(hb)

# Fin
ok = Gtk::Button.new(Gtk::Stock::CLOSE)
ok.signal_connect('clicked') { Gtk.main_exit }
vb.pack_start(ok)

window.add(vb)
window.show_all

Gtk.main
Image non disponible Image non disponible

Pour concevoir l'interface graphique, avant de commencer à créer les widgets, il faut d'abord avoir une idée de ce dont on a besoin. Pour cela, un papier et un crayon (ou mieux, un tableau blanc) peuvent être utiles. Dans notre cas, nous aurons besoin de deux éléments correspondant aux valeurs en Francs et en Euros, ainsi que d'un bouton pour fermer la fenêtre. Cela nous définit déjà une boîte verticale contenant trois rangées. Ensuite, chaque élément sera composé d'une étiquette (label) pour le décrire, d'une boîte de texte pour la saisie, et d'un bouton pour valider (bien que l'on puisse aussi valider au clavier. Donc, dans une rangée de la boîte verticale, nous aurons une boîte horizontale composée de trois colonnes. Il aurait aussi été possible (et préférable dans ce cas) de faire cela avec une table, mais nous voulions montrer comment imbriquer des conteneurs. En exercice, refaites la même chose avec une table, et trouvez pourquoi c'était préférable.

Le reste de la construction des widgets ne devrait pas poser problème. Voyons maintenant la gestion des évènements. Nous voulons afficher un message contenant la conversion, lorsque l'utilisateur a tapé un montant. Pour cela, nous avons placé des boutons à droite des boîtes de saisie, mais nous voudrions aussi pouvoir valider en tapant sur la touche entrée. Nous allons donc attribuer un bloc au signal "activate" de chaque boîte de texte, qui sera chargé d'afficher le message. Ensuite, nous attribuerons un bloc au signal "clicked" des boutons, et ce bloc se contentera d'appeler la méthode activate de la boîte de texte correspondante. En faisant cela, on déclenche l'émission du signal "activate", et donc l'appel du bloc qui lui a été attribué.

En guise d'exercice, vous pouvez faire le même programme, qui change la valeur des boîtes de texte au lieu d'afficher une fenêtre de message. Quand on entre une valeur dans la boîte "Francs" et que l'on valide, affichez le résultat de la conversion dans la boîte "Euros".

V. Conclusion

Nous avons vu l'organisation générale d'un programme graphique utilisant GTK, et quelques uns des éléments qui constituent une interface. Cela est suffisant pour faire de petits programmes d'appoint, mais nous verrons dans les parties suivantes des widgets plus évolués, ainsi que les éléments nécessaires pour construire une interface graphique complète, avec ses menus, ses barres d'outils, ses raccourcis clavier, etc.

À suivre...