Premier pas en programmation d'applications graphiques en python avec glade (sous GNU/Linux)

Besoin

Python est un langage clair, facile à apprendre et à déboguer. Son seul inconvénient est d'être interprété, ce qui le rend moins efficace que le C/C++ pour les applications lourdes et complexes et, sans doute, un peu moins portable, mais il est très attrayant. Son utilisation à la console en mode texte est sans complication et répond déjà à de nombreux besoins.

Cependant, lorsqu'elles sont jugées nécessaires, les interfaces graphiques fenêtrées sont longues et fastidieuse à construire "à la main". C'est là que glade intervient. Cette application est un atelier de construction de fenêtres au moyen d'éléments "préfabriqués". Après avoir disposé ces éléments, il suffit de définir les signaux qu'ils envoient lorsqu'ils sont mis en œuvre et d'enregistrer l'architecture de la fenêtre dans un fichier glade. Un programme python peut alors associer des fonctions aux signaux définis, afin d'effectuer les actions voulues.

Il existe des versions empaquetées de python, de glade et des bibliothèques de liaison entre les deux dans les principales distributions GNU/Linux et tout cela s'installe en quelques minutes de façon classique avec un gestionnaire de paquets. Mi-juin 2014, sous Lucid Lynx, par défaut, Synaptic propose les versions 3.6.7 de Glade, 2.6.5 de python et 2.17.0 du paquet de liaison python-glade2. Cette LTS d'Ubuntu n'est plus maintenue mais conservée par l'auteur de cette page sur son ordinateur de production (pour les raisons compréhensibles en lisant ceci, ceci et cela).

Mon premier est glade

A l'ouverture de glade, l'application propose de régler les préférences du projet automatiquement ouvert mais non encore enregistré, bien sûr. Pour les projets simples, il est possible de laisser les options par défaut sauf peut-être le format du fichier projet à choisir entre GtkBuilder et Libglade. Par défaut, glade 3 propose le format GtkBuilder plus moderne que Libglade. Pour commencer, c'est le format Libglade qui est choisi dans glade, avec le code python correspondant. L'utilisation de GtkBuilder est décrite au bas de la présente page.
choix pour le projet glade

Après la fermeture de cette fenêtre, l'utilisateur se trouve face à la fenêtre principale de glade, divisée en quatre parties.
glade initial vide

La meilleure façon d'apprendre étant par l'exemple, une petite boîte de dialogue très utile le matin (;-) va être construite.

Il faut d'abord placer l'élement réceptacle qui va contenir tout le reste, par un clic sur le widget disponible de niveau supérieur nommé "Boîte de dialogue" (le nom se voit au survol), ce qui provoque son insertion dans le panneau de travail, au milieu, et l'affichage de l'arborescence et des caractéristiques du widget, à droite.
widget disponible
affichage et description du widget

Il est possible de sélectionner les éléments en cliquant dessus dans le panneau de travail ou bien dans l'arborescence. Ils sont nommés par défaut par glade mais il peut être souhaitable de changer les noms pour rendre plus lisible la conception de l'interface.

On va donc ici nommer la boîte de dialogue en remplaçant dialog1 par son nouveau nom ptidej et déclarer qu'elle est visible.
nommage et affichage du widget

Puisque le projet commence à exister, il est prudent de l'enregistrer, dans un dossier spécifique au projet, avec un nom judicieux (en xxx.glade), avant d'aller plus loin.
enregistrement du projet

Ceci fait, en cliquant alternativement sur l'élément graphique voulu et sur la zone de destination, on ajoute à la boîte de dialogue successivement :

L'élément le plus difficile à utiliser est l'ensemble de boutons radio, qui mérite d'être un peu détaillé. Déjà, dès la présente étape de construction graphique, il faut faire une première manipulation indispensable. En effet, le principe même des boutons radio est qu'ils fonctionnent par groupe. Dans un groupe, un seul bouton radio peut être coché à la fois. La méthode utilisée par glade pour obtenir ce résultat est conçue pour éviter les redondances et donc, pour construire un groupe, il faut procéder de la manière suivante :

A ce stade, le concepteur d'interface présente dans le panneau de travail une boîte de dialogue complète mais complètement inerte.
la boite de dialogue construite
Pour l'utiliser en liaison avec une application, il faut la rendre vivante, de façon qu'elle crie "Aïe !" lorsqu'on la pince...

Cela est facile car glade permet de dire à chaque objet d'émettre un signal lorsqu'il subit une action. Pour utiliser cette possibilité, il faut aller dans l'onglet "Signaux" du panneau des propriétés de chaque objet considéré.

signal de case a cocher
Ainsi, la case à cocher ("toggle button" en anglais) nommée "option1" peut émettre un signal qui a pour nom "toggled" et pour lequel une boîte déroulante permet de choisir une valeur, par exemple "on_option1_toggled", qui est alors simplement la chaîne de caractères choisie par le programmeur pour être émise lorsque la case à cocher change d'état, c'est-à-dire aussi bien lorsqu'elle devient cochée que lorsqu'elle devient décochée. Sur détection de cette valeur, le programme (écrit par ailleurs en python), pourra accomplir une fonction... Pour information, l'extrait du fichier xml .glade produit par glade relatif à cette boîte à cocher est :

<widget class="GtkCheckButton" id="option1">
     <property name="label" translatable="yes">Supplément de lait</property>
     <property name="visible">True</property>
     <property name="can_focus">True</property>
     <property name="receives_default">False</property>
     <property name="draw_indicator">True</property>
     <signal name="toggled" handler="on_option1_toggled"/>
</widget>

A l'avant-dernière ligne, "toggled" est bien le nom du signal et "on_option1_toggled" en est l'attribut handler, mal traduit dans glade par "gestionnaire" (mot qui évoque un agent) alors qu'il serait plus indiqué de le rendre en français par "valeur de contrôle" ou plus simplement "contrôle" pour évoquer une valeur statique.

Encore une fois, il faut faire un zoom sur les boutons radio qui constituent un cas particulier. Il est possible dans glade de leur associer un signal "toggled" de la même manière que pour la case à cocher. Cependant, ces boutons fonctionnent en groupe et sont mutuellement exclusifs. Autrement dit, dans un groupe, un seul bouton peut être coché à la fois. En conséquence, lorsqu'un bouton est cliqué pour être rendu actif, deux choses se produisent successivement :

Par suite, deux signaux sont émis à chaque clic sur un bouton radio.

L'extrait du fichier produit par glade relatif aux trois widgets représentant les trois boutons radio est :

<widget class="GtkRadioButton" id="choix1">
     <property name="label" translatable="yes">Thé</property>
     <property name="visible">True</property>
     <property name="can_focus">True</property>
     <property name="receives_default">False</property>
     <property name="active">True</property>
     <property name="draw_indicator">True</property>
     <signal name="toggled" handler="on_choix1_toggled"/>
</widget>
<widget class="GtkRadioButton" id="choix2">
     <property name="label" translatable="yes">Café</property>
     <property name="visible">True</property>
     <property name="can_focus">True</property>
     <property name="receives_default">False</property>
     <property name="draw_indicator">True</property>
     <property name="group">choix1</property>
     <signal name="toggled" handler="on_choix2_toggled"/>
</widget>
<widget class="GtkRadioButton" id="choix3">
     <property name="label" translatable="yes">Chocolat</property>
     <property name="visible">True</property>
     <property name="can_focus">True</property>
     <property name="receives_default">False</property>
     <property name="draw_indicator">True</property>
     <property name="group">choix1</property>
     <signal name="toggled" handler="on_choix3_toggled"/>
</widget>

Cet extrait montre que les trois boutons ont un signal de même nom ("toggled") et que chaque bouton, d'identité du type "choixN", a bien sa propre valeur de contrôle (handler) du type "on_choixN_toggled". Pourtant, au moins avec les versions logicielles utilisées ici, la liaison du signal vers la fonction codée en python n'arrive pas à se faire automatiquement et doit être programmée manuellement. Pour voir cela, il est temps de passer au versant python de l'application.

Mon second est python

Dans son principe, le code python est très bref. Le choix fait ici est de créer une classe d'applications, dotées de fonctions de rappel classiques ou spécifiques.

L'une des fonctions classiques est __init__, qui est automatiquement appelée une fois à la création d'une instance de la classe. Ici, elle contient notamment l'établissement des liens entre les signaux engendrés par les éléments graphiques (grâce à glade) et les fonctions de rappel de l'instance d'application engendrée. L'établissement de ces liens est en deux parties.

Pour les éléments autres que les boutons radio, la connexion est faite automatiquement par la méthode signal_autoconnect de l'objet gtk.glade.XML, à laquelle est fourni un dictionnaire python des fonctions de rappel. L'extrait correspondant du code est :

self.widgets = gtk.glade.XML('ptidej2.glade',"ptidej") # recupere les widgets dans un objet
events = \
   {  'on_confirmation_clicked': self.confirme,   # signal du bouton "Confirmer", quand il est cliqué
     'on_ptidej_delete_event': self.quitte,      # signal de la boite de dialogue, au clic sur x de la barre de titre
     'on_fermeture_clicked': self.quitte,      # signal du bouton "Fermer", quand il est cliqué
     'on_option1_toggled':self.traiteOption   # signal de la case cochable "option1", au changement d'etat
   }
# le dictionnaire events associe a chaque signal une fonction de rappel, en vue de l'autoconnect
self.widgets.signal_autoconnect(events) # l'autoconnect prend bien en compte la case cochable...

Pour les boutons radio, la connexion est faite "manuellement", par des instructions comme

self.widgets.signal_connect('on_choix1_toggled',self.noteChoix,1)

qui associe au signal on_choix1_toggled la fonction de rappel noteChoix en lui transmettant en argument la valeur 1. Ceci peut être fait efficacement par une boucle en une ligne pour les trois boutons :

for i in xrange(1,4): self.widgets.signal_connect('on_choix'+str(i)+'_toggled',self.noteChoix,i)

Une autre chose utile à signaler est que, pour les boutons à bascule, le signal émis ne dit pas dans quel sens se fait la bascule. Pour programmer l'action dans la fonction de rappel, il faut donc tester dans quel état se trouve le bouton après l'émission du signal. Pour la case à cocher, c'est simple avec la méthode get_active() du widget :

def traiteOption(self,widget):
   if widget.get_active():
      self.option = " Avec du lait en plus !"
   else:
      self.option = " Sans lait en plus !"

Pour les boutons radio, c'est un peu plus compliqué puisque deux signaux sont émis à chaque changement d'état du groupe de boutons. Il faut donc faire deux tests pour déterminer le bouton émetteur et son état, par exemple par :

if (widget.get_active() and (ichoix == 1)):      # ici on teste ^^les deux^^ widgets emetteurs
   self.commande = "Commande de thé en cours !"   # et on ajuste la variable commande au widget devenu actif...

Mon tout est une application graphique

Au total, pour l'exécution, glade n'est plus nécessaire. Il suffit de placer dans un même dossier les deux fichiers de l'application :

Le lien entre les deux est inscrit dans le programme en python par l'instruction :

self.widgets = gtk.glade.XML('ptidej2.glade',"ptidej")

qui désigne le fichier glade et tire tous les widgets de la fenêtre indiquée en second argument pour les placer dans un objet à disposition du programme python. Ce dernier peut se lancer classiquement, après avoir été rendu exécutable, par une commande au terminal. Grâce aux modules gtk et gtk.glade importés, il construit l'interface graphique et gère les interactions avec elle. Les illustrations suivantes le présentent en pleine action, au lancement puis après activation d'un bouton radio et de la case à cocher et clic sur le bouton "Confirmer".

application en action avant confirmation application en action apres confirmation

Et avec GtkBuilder ??

La programmation qui précède est cohérente avec la génération Gnome 2/GTK+ 2, encore répandue, mais qui représente quand même le passé. Pour regarder vers l'avenir, il est préférable d'utiliser GtkBuilder.

glade

D'une part, dans glade, sans rien changer à la conception de l'interface, le fichier est enregistré au format GtkBuilder. Le fichier porte la même extension mais, à l'ouverture, présente quelques différences. Par exemple, l'extrait XML relatif à un bouton radio prend la forme suivante.

<object class="GtkRadioButton" id="choix1">
     <property name="label" translatable="yes">Thé</property>
     <property name="visible">True</property>
     <property name="can_focus">True</property>
     <property name="receives_default">False</property>
     <property name="use_action_appearance">False</property>
     <property name="active">True</property>
     <property name="draw_indicator">True</property>
     <signal name="toggled" handler="on_choix1_toggled" swapped="no"/>
</object>

Par comparaison avec l'extrait donné plus haut, il apparait que widget est devenu object. Le signal reçoit un atribut swapped supplémentaire mais son nom et son contrôle n'ont pas changé et c'est l'essentiel ici.

python

D'autre part, le programme python est différent (noter que dans la version présentée ci-après, le code est simplifié et n'utilise pas de classe pour l'application mais la programme directement, afin de ne pas introduire de confusion avec la classe de gestionnaire, mais ce point n'est pas essentiel).

Le lien avec le fichier glade se fait par un code qui appelle Gtk.Builder().

widgets = Gtk.Builder()               # cree un objet pour ...
widgets.add_from_file("ptidej3-GtkB.glade")          # ... recuperer les widgets dedans

Ensuite, la connexion automatique fonctionne parfaitement, y compris pour les boutons radio, à la différence du cas précédent. Une manière élégante de la réaliser est de créer une classe d'objet "gestionnaire de signaux" qui rassemble toutes les méthodes nécessaires, chaque méthode portant le même nom que le signal qu'elle gère (du coup, la traduction de handler par "gestionnaire" dans glade se comprend). Par exemple :

def on_choix1_toggled(self,widget):
   if (widget.get_active()):
      self.commande = "Commande de thé en cours !"
   return True

Ensuite, il suffit de mettre en œuvre cette classe avec la méthode de connexion des signaux de Gtk.Builder :

widgets.connect_signals(Gestionnaire())

Le code est plus simple, plus lisible et ça marche!

Les deux fichiers constituant l'application sont :

Ces versions téléchargeables sont adaptées à GTK+ 2 mais facilement adaptables à GTK+ 3 si l'on dispose de cet environnement (pour glade, prendre la dernière version disponible et, pour python, voir les commentaires en tête du fichier joint).
application GtkBuilder en action

Astuce pour les boutons radio

Si la liste de choix par bouton radio est un peu longue, il devient fastidieux et peu lisible de programmer autant de gestionnaires de signaux que de boutons. Cependant, rien n'interdit de faire émettre la même valeur de contrôle par tous les boutons d'un même groupe et il convient alors de programmer un seul gestionnaire de signaux. Ce gestionnaire "attrape" le bouton actif par la méthode "get_active()" et peut aussi se servir de l'étiquette rédigée sur le bouton radio grâce à la méthode "get_label()". Exemple :

def on_choix_toggled(self,widget):
   if (widget.get_active()):
      self.commande = "Commande de " + widget.get_label() + " en cours !"
   return True

Fichiers d'exemple complets :

Pour aller plus loin

Un logiciel en kit python2_gtk2_glade3_kit.tar.gz pour disposer d'éléments préfabriqués pour construire une application gtk (donner les droits d'exécution aux fichiers .py et .sh pour tester).


Retour à l'accueil LINUX


logo html 5  Validé avec le vérificateur expérimental du W3C