Créer des interfaces graphique avec PyGTK et Glade

nj

pygtk-glade

- Titre : Créer des interfaces graphique avec PyGTK et Glade

- Auteur : nj

- Parution : 5 février 2008

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Créer des interfaces graphique avec PyGTK et Glade

- II. Construire une application avec PyGTK et Glade

- II-A. La barre de menu

- II-B. Création du dialogue

- II-C. The Python Code

- II-D. Tree Views et List Stores

- II-E. Rassembler les pièces du puzzle

- III. Améliorer notre application PyGTK

- III-A. L'interface graphique - Glade

- III-B. Le code source

- III-C. Enregistrer et charger la liste des vins

- III-C-1. Enregistrer

- III-D. Charger

- III-E. Conclusion

I - Créer des interfaces graphique avec PyGTK et Glade

Où l'on va découvrir un framework multiplateforme de création d'interface graphique basé sur GTK+.

Ce billet est la traduction, librement adaptée, du billet anglophone Creating a GUI using PyGTK and Glade

Après avoir passé du temps à créer des interfaces graphiques à l'aide de TKinter et ceci très facilement, mais restant frustré par le mélange entre mon code et l'interface graphique, j'ai décidé de m'intéresser à un autre outil de création d'interfaces graphique. Après un moment d'exploration des différentes possibilités, je me suis décidé sur l'utilisation de PyGTK et Glade.

La raison de mon choix pour ces deux technologies est qu'elles sont multi-plateformes et que l'utilisation de Glade satisfait mon souhait de séparer le code de mon application de la partie interface graphique.

Si vous n'avait jamais entendu parlé de Glade avant, c'est un "Editeur d'Interface Graphique pour GTK+ et Gnome". Il génére des fichiers XML qui contiennent la description des interfaces graphique.

Le site web de PyGTK décrit ce qu'est PyGTK :

PyGTK fournit une interface Python pratique à utiliser pour la bibliothèque
GTK+, prenant en charge tout les détails assomants tels que la gestion de la
mémoire et les transtypages. Combiné avec PyORBit et gnome-python, elle
peut être utilisé afin d'écrire des applications Gnome riches.

Bien, nous y voici, j'écris ceci sur mon systéme Debian fraîchement installé (NdT : et votre serviteur sur sa Ubuntu Edgy bien à jour ;). Si vous utilisez une distribution Linux Debian, ou un dérivé Debian (NdT : telle que Ubuntu, etc.), installer PyGTK et Glade est très simple :

apt-get install python-gtk2 python-glade2

Maintenant, il est temps de créer notre première interface graphique simple. Voici l'accueil de Glade lorsque vous le démarrez :

Pour créer notre fenêtre de base, il faut presser le bouton "Window" qui se trouve sur la palette d'outils de Glade. Nous pouvons alors modifier les propriétés de cette fenêtre depuis le formulaire "Properties" :

Nous appellerons notre fenêtre Mainwindows et nous lui donnerons le titre (title) "Hello World... Again!".

Si vous avez l'habitude d'utiliser un environnement de développement intégré, par exemple Visual Studio, alors l'utilisation de Glade pourra vous paraître un peu étrange les premiers temps. Tout spécialement parce que vous ne pourrez pas placer les contrôles n'importe comment sur la fenêtre, mais que vous devrez utiliser des containers de placement pour les aligner. C'est d'autant plus étrange (au moins pour moi) que ceci est la méthode la plus couramment rencontrée dans les outils de construction d'interfaces graphique, et que des applications comme Visual Studio sont plutôt l'exception.

Revenons à notre tutoriel. La première chose que nous allons ajouter est un texte pour dire à l'utilisateur de cliquer sur le bouton (bien sûr, on pourrait mettre ce texte dans le bouton même, mais c'est moins amusant de n'avoir qu'un seul widget). Comme GTK utilise des containers pour aligner les widgets, la première chose que nous devons faire est de d'ajouter un container. Nous placerons notre texte au dessus du bouton, alors utilisons une Vertical Box avec 2 rangées. Pour ajouter la Vertical Box, cliquez simplement sur celle-ci dans la palette Glade, et ensuite cliquez dans notre fenêtre MainWindow. Un dialogue vous demandera alors combien de rangées vous voulez, et dans notre cas, nous en voulons deux.

L'action suivante sera d'ajouter le texte en cliquant sur Label dans la palette Glade et ensuite en cliquant dans la première rangée du container Vertical Box que nous venons de créer. Nous conservons le nom par défaut (label1), mais nous changeons le texte pour : "Please click on the button!". Le changement du texte se fait par l'intermèdiaire du formulaire Properties, qui, vous l'aurez remarqué, affiche et permet de modifier les propriétés du widget qui est sélectionné.

Ensuite, nous ajoutons un bouton de la même manière que pour le Label, excepté que nous le déposons dans la seconde rangée. Nous appellerons ce bouton btnHelloWorld, et il aura le texte "Click me!"

Il faut maintenant configurer les options de notre projet. J'appellerais ce projet PyHelloWorld et le sauverais dans mon dossier projects/PyHelloWorld. N'oubliez pas de sauver le projet.

Vous pourrez constater que deux fichiers ont été créés dans le dossier PyHelloWorld. L'un est le fichier projet de Glade avec l'extension .gladep et l'autre est notre interface graphique dans un fichier XML, avec l'extension .glade.

Maintenant, nous allons avoir besoin d'un programme Python pour charger et afficher le fichier .glade. Pour cela, dans le même dossier, je créé un fichier nommé PyHelloWorld.py.

La première chose à faire est d'importer toutes les bibliothèques nécessaire à notre projet :

#!/usr/bin/env python
import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gtk
import gtk.glade
except:
sys.exit(1)

Ensuite, nous crééons notre classe principale, je vais l'appeler HellowWorldGTK. Nous utilison la méthode init pour charger le fichier glade :

class HellowWorldGTK:
"""This is an Hello World GTK application"""

def __init__(self):

#Set the Glade file
self.gladefile = "pyhelloworld.glade"
self.wTree = gtk.glade.XML(self.gladefile)

#Get the Main Window, and connect the "destroy" event
self.window = self.wTree.get_widget("MainWindow")
if (self.window):
self.window.connect("destroy", gtk.main_quit)

La première chose qui est faite (après la définition de la classe) c'est indiquer le fichier glade que nous allons utiliser et créer un objet gtk.glade.XML pour cela. Voici la description de l'objet, reprise des références de pyGTK2 :

Cet objet représente une `instance` de la description d'une interface XML.
Quand un de ces objets est créé, le fichier XML est lu, et l'interface est créée.
L'objet gtk.glade.XML fournit alors une passerelle pour accéder aux widgets de
l'interface graphique par leur noms au sein de la description XML. L'objet
gtk.glade.XML peut aussi être utilisé pour connecter des fonctions aux
signaux. Libglade met à disposition une interface avec laquelle il est possible
de connecter les signaux aux fonctions de manière automique, ceci en
parcourant la table des symboles du programme.

Ce qui est fait lorsque nous instancions notre objet gtk.glade.XML, c'est créer et charger notre interface graphique.

L'étape suivante et de récupérer l'instance de notre fenêtre principale et de connecter l'événement "destroy" sur la fonction gtk.main_quit(). Ceci permettra de quitter notre application lorsque l'on fermera la fenêtre principale. Sans ceci, l'application continuerait de s'exécuter alors que la fenêtre principale sera fermé (évidemment, ce n'est pas ce que nous voulons).

C'est tout pour la classe HellowWorldGTK, la prochaine étape qui est nécessaire et de créer une instance de notre classe et de démarrer la boucle principale GTK:

if __name__ == "__main__":
hwg = HellowWorldGTK()
gtk.main()

On y est, c'est plutôt facile, si vous exécuter ce fichier, la fenêtre GTK apparaîtra et se contentera, pour toute action, de vous permettre de quitter proprement quand vous fermerez la fenêtre.

La prochaine étape est la connexion de l'événement de clic sur le bouton à une fonction. Pour cela, nous utiliserons à nouveau Glade.

Dans notre fenêtre, nous sélectionnons l'objet bouton puis, dans le formulaire des propriétés, nous choisissons l'onglet "Signals". C'est à cet endroit que l'on va ajouter un nouveau signal en cliquant sur le bouton (...) et en choisissant "clicked". Ceci va créer un gestionnaire nommé "on_btnHelloWorld_clicked". Nous pourrions changer son nom, mais sa valeur par défaut convient pour l'instant.

Nous en avons terminé avec Glade, maintenant, il nous reste à connecter cet événement à quelquechose dans notre code. Heureusement, c'est facilement réalisable par l'intermédiaire de la fonction gtk.glade.XML.signal_autoconnect

#Create our dictionay and connect it
dic = { "on_btnHelloWorld_clicked" : self.btnHelloWorld_clicked,
"on_MainWindow_destroy" : gtk.main_quit }
self.wTree.signal_autoconnect(dic)

En simplifiant, le dictionnaire est créé en utilisant le nom de de l'événement, et la fonction qui est sera connecté. Vous pouvez voir que l'on connecte l'événement du clic sur le bouton à une nouvelle fonction, et que nous connectons l'événement "on_MainWindow_destroy" à la fonction gtk.main_quit() . Ceci remplace notre code précédent qui quittait le programme à la fermeture de la fenêtre.

Si vous voulez utiliser cette version du dictionnaire d'événements, vous devrez utiliser Glade pour ajouter un événement destroy dans les signaux de la fenêtre.

Comme certains d'entre vous pourrait passer à côté, j'ai ajouté une image qui rend l'opération plus évidente :

Il faut ensuite créer la fonction btnHelloWorld_clicked dans la classe HellowWorldGTK :

def btnHelloWorld_clicked(self, widget):
print "Hello World!"

Simplissime! Maintenant, quand nous l'exécutons et que nous cliquons sur le bouton "Click Me!", nous voyons "Hello World!" s'écrire sur le ligne de commande.

Cette leçon est terminée, et pour finir, je dirais que j'aime vraiment tout ce qui se fait avec PyGTK et Glade. Voici le code source complet :

#!/usr/bin/env python

import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gtk
import gtk.glade
except:
sys.exit(1)

class HellowWorldGTK:
"""This is an Hello World GTK application"""

def __init__(self):

#Set the Glade file
self.gladefile = "pyhelloworld.glade"
self.wTree = gtk.glade.XML(self.gladefile)

#Create our dictionay and connect it
dic = { "on_btnHelloWorld_clicked" : self.btnHelloWorld_clicked,
"on_MainWindow_destroy" : gtk.main_quit }
self.wTree.signal_autoconnect(dic)

def btnHelloWorld_clicked(self, widget):
print "Hello World!"

if __name__ == "__main__":
hwg = HellowWorldGTK()
gtk.main()

Liens utiles

- http://www.linuxjournal.com/article/6586

- http://www.async.com.br/~kiko/pygtk-web/articles/bitpodder/BitPodder.htm

- http://www.linuxjournal.com/article/7421

- http://www.pygtk.org/articles.html

- http://www.pygtk.org/tutorial.html

II - Construire une application avec PyGTK et Glade

Où l'on va commencer l'écriture d'une application simple et utile.

Ce billet est la traduction, librement adaptée, du billet anglophone Building an Application with PyGTK and Glade.

Après avoir abordé PyGTK et Glade dans mon premier tutoriel, j'ai décidé d'écrire un autre tutoriel plus complexe. Ne pouvant me décider sur le thème à aborder (Note : Si vous avez des suggestions pour d'autres tutoriels ou billets je se ravis que vous m'en touchiez deux mots), j'ai décidé de travailler à la création d'une application simple à partir de laquelle des sujets utiles seront dévoilés.

L'idée qui m'est venue (qui, heureusement, sera simple) est de créer un programme qui me permettra de faire le suivi des différents types de vin que je consomme ainsi que de connaître mes préférés. C'est quelque chose que je voulais écrire depuis une moment et je pense qu'il serait bien de faire cela avec PyGTK.

Le projet s'appellera PyWine. Les sources complètes et le fichier Glade de ce tutoriel sont stockés ici

Premièrement, démarrons une nouveau projet dans Glade, il sera enregistré dans un dossier PyWine. Créons une nouvelle fenêtre nommée "mainWindow" et modifions son titre en "PyWine". Puis nous ajoutons un gestionnaire sur le signal Destroy, exactement comme dans le premier tutorial (NdT : Créer des interfaces graphique avec PyGTK et Glade).

Ensuite, j'ajoute une Vertical Box à 4 rangées dans la fenêtre. Dans l'ordre, du haut vers le bas, nous aurons une rangée pour la barre de Menu, une pour la barre d'outils (Toolbar), une autre pour une liste arborescente (List or Tree View), et la dernière rangée contiendra une barre de status (Status bar). Le Tree View sera nommé "wineView".

La liste arborescente que nous avons placé dans la fenêtre sera utilisée pour afficher les informations sur les vins. Donc, la première chose que nous voulons faire est de permettre à l'utilisateur d'ajouter un vin. Pour cela, nous utiliserons une option du menu ou un bouton de la barre d'outils.

Ajoutons un bouton dans la barre d'outils. Pour ce faire, il suffit de sélectionner Toolbar button dans la palette de Glade et de cliquer sur un bouton de la barre d'outils de notre fenêtre mainWindow. Ceci devrait poser un bouton à l'aspect étrange dans la barre d'outils. Editons les propriétés de ce bouton par le formulaire "Properties". Nommons le "tbAddwine", donnons lui le texte "Add Wine" et utilisons une icône Add.

(NdT : Si vous ne trouvez pas Toolbar button dans la palette de Glade, c'est qu'il vous faut une version plus récente. Personnellement, j'utilise la version 2.10 de Glade).

Ajoutons maintenant un gestionnaire pour l'événement de clic sur le bouton tbAddWine, mais cette fois, changeons le nom par défaut "on_tbAddWine_clicked" en "on_AddWine".

II-A - La barre de menu

Travaillons maintenant sur le menu, comme nous voulons que l'utilisateur puisse aussi l'utiliser pour ajouter des éléments. Cliquons sur la barre de menu de notre fenêtre et réglons ses propriétés. Cliquez sur le bouton "Edit Menus..." pour modifier le contenu de la barre de menu.

Cliquez sur le bouton Add pour ajouter un nouvel élément dans le menu, et modifiez son label en "_Add". Ensuite, sélectionnez l'élément Add du menu et cliquez sur le bouton "Add Child". Ceci va créer un sous-menu dans le menu Add. Modifiez le label en "_Wine" et le gestionnaire en "on_AddWine".

Remarquez bien que le gestionnaire d'événement pour le menu Add->Wine et le bouton "Add" est le même "on_AddWine". C'est parce que ces deux widgets déclencheront la même action, ajouter un vin à notre liste.

Ce qui va apparaître quand l'utilisateur cliquera sur le bouton "Add Wine" ou l'option de menu Add->Wine, c'est un dialogue lui permettant de saisir les détails sur le vin. S'il choisit de valider le dialogue par "OK", alors son vin sera ajouté à la liste.

II-B - Création du dialogue

Bien, la chose à faire maintenant est de créer le dialogue d'ajout d'un nouveau vin. Pour cette exemple, nous resterons simple et l'utilisateur pourra saisir le nom du vin, son producteur, son année de mise en bouteille et la variété de raisin.

Pour créer un nouveau dialogue, il suffit de cliquer sur le bouton Dialog dans la palette de Glade. Ceci dervait afficher le formulaire de création de nouveau dialogue, choisissez l'option "Standard button layout" avec les boutons Cancel et OK. Modifiez le nom du dialogue en "wineDlg" et son titre en "Add Wine".

Nous utiliserons une table afin de gérer l'alignement des choses sur notre wineDlg. Ajoutez une table au dialogue (de la même manière que l'on ajoute un widget à une fenêtre) et réglez le nombre de rangées à 4 et le nombre de colonnes à 2. Ensuite, nous remplissons les emplacements de la table avec des Label et des Text Entry jusqu'à ce que notre dialogue ressemble à ceci :

J'ai ajouté 3 pixels d'espacement entre les rangées de la table. Ce réglage se trouve dans les proriétés de la table. Si vous avez des difficultés pour sélectionner la table, il est facile d'afficher l'arborescence des widgets par le menu de Glade View->Show Widget Tree, et de cliquer sur la table. Vous pouvez aussi enfoncer la touche Shift pendant que vous cliquez sur sur le dialogue. Ceci permet de parcourir les widgets de manière cyclique.

Il faut maintenant nommer tous les widgets d'édition du dialogue : enWine, enWinery, enGrape, et enYear.

II-C - The Python Code

Bien, entrons dans le vif du sujet et faisons fonctionner le code, nous appelons notre fichier source pywine.py et le créeons dans le dossier /projects.Pywine (le même dossier qui contient déjà nos fichiers Glade). Ce qui suit est le code source de base qui sera utilisé (récupéré du premier tutoriel - Créer des interfaces graphique avec PyGTK et Glade) :

#!/usr/bin/env python

import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gtk
import gtk.glade
except:
sys.exit(1)

class pyWine:
"""This is the PyWine application"""

def __init__(self):

#Set the Glade file
self.gladefile = "pywine.glade"
self.wTree = gtk.glade.XML(self.gladefile, "mainWindow")

#Create our dictionay and connect it
dic = {"on_mainWindow_destroy" : gtk.main_quit
, "on_AddWine" : self.OnAddWine}
self.wTree.signal_autoconnect(dic)

def OnAddWine(self, widget):
"""Called when the use wants to add a wine"""

print "OnAddWine"

if __name__ == "__main__":
wine = pyWine()
gtk.main()

Il y a quelques ajouts à ce code. Le premier ajout est un gestionnaire pour le signal "on_AddWine", si vous exécutez ce programme, vous constaterez qu'un message apparaît sur la console si vous cliquez sur le bouton Add Wine ou que vous activez le menu Add->Wine. L'autre ajout au code est le passage du nom de la fenêtre principale à gtk.glade.XML. Ceci permet de ne charger que cette fenêtre et ses enfants. (NdT : On évite ainsi d'afficher toutes les fenêtres contenues dans le fichier Glade).

Ce que nous faisons ensuite, c'est de créer une classe Wine qui sera utilisée pour stocker les informations sur les vins :

class Wine:
"""This class represents all the wine information"""

def __init__(self, wine="", winery="", grape="", year=""):

self.wine = wine
self.winery = winery
self.grape = grape
self.year = year

Maintenant, créeons une classe que nous utiliserons afin d'afficher notre dialogue wineDlg et appelons-la wineDialog :

class wineDialog:
"""This class is used to show wineDlg"""

def __init__(self, wine="", winery="", grape="", year=""):

#setup the glade file
self.gladefile = "pywine.glade"
#setup the wine that we will return
self.wine = Wine(wine,winery,grape,year)

Ajoutons maintenant une méthode à notre classe wineDialog afin de charger le widget wineDialog depuis le fichier glade et de l'afficher. Nous voulons aussi que cette fonction nous retourne le résultat du dialogue, qui sera un gtk.RESPONSE, vous pourrez en apprendre plus sur le site web de PyGTK.

Voici la méthode run :

def run(self):
"""This function will show the wineDlg"""

#load the dialog from the glade file
self.wTree = gtk.glade.XML(self.gladefile, "wineDlg")
#Get the actual dialog widget
self.dlg = self.wTree.get_widget("wineDlg")
#Get all of the Entry Widgets and set their text
self.enWine = self.wTree.get_widget("enWine")
self.enWine.set_text(self.wine.wine)
self.enWinery = self.wTree.get_widget("enWinery")
self.enWinery.set_text(self.wine.winery)
self.enGrape = self.wTree.get_widget("enGrape")
self.enGrape.set_text(self.wine.grape)
self.enYear = self.wTree.get_widget("enYear")
self.enYear.set_text(self.wine.year)

#run the dialog and store the response
self.result = self.dlg.run()
#get the value of the entry fields
self.wine.wine = self.enWine.get_text()
self.wine.winery = self.enWinery.get_text()
self.wine.grape = self.enGrape.get_text()
self.wine.year = self.enYear.get_text()

#we are done with the dialog, destroy it
self.dlg.destroy()

#return the result and the wine
return self.result,self.wine

Remarquez que nous chargeons la fenêtre du dialogue de la même manière que la fenêtre principale. Nous appelons gtk.glade.CML() et lui passons le nom du widget que nous voulons charger. Ceci affichera automatiquement le dialogue (comme pour notre fenêtre principale), mais ce n'est pas suffisant, car il faut impérativement que la mèthode run attende que l'utilisateur quitte le dialogue avant de continuer son traitement. Pour cela, il nous faut récupérer le dialogue depuis l'arborescence des widgets (self.dlg = self.wTree.get_widget("wineDlg")) et appeler la fonction run des GTkDialogs. Voici ce que la documentation de PyGTK raconte à propos de cette fonction :

La méthode run() exécute une boucle récursive jusqu'à ce que le dialogue émette un signal "response", ou soit détruit. Si le dialogue est détruit, la méthode run() retourne gtk.RESPONSE_NONE; sinon, elle retourne l'identifiant de la réponse du signal "response" émis. Avant d'entrer dans sa boucle, la méthode run() appelle pour vous la méthode gtk.Widget.show(). Notez bien qu'il vous faudra éventuellement afficher vous même les enfants du dialogue.

Durant l'exécution de la méthode run(), le comportement par défaut de "delete_event" est désactivé; si le dialogue reçoit un "delete_event", il ne sera pas détruit comme une fenêtre le serait habituellement, et la méthode run() retournera gtk.RESPONSE_DELETE_EVENT. D'ailleurs, pendant 'exécution de la méthode run(), le dialogue sera modal. Vous pouvez forcer l'arrêt de la méthode run() en appelant response() pour émettre un signal "response". Détruire le dialogue pendant l'exécution de run() est une très mauvaise idée, parce que votre code suivant l'appel de run() ne saura pas si le dialogue a été détruit ou pas.

Après le retour de la méthode run(), vous devez vous même cacher ou détruire le dialogue, selon vos besoins.

Le bouton Ok va retourner gtk.RESPONSE_OK et le bouton Cancel retournera gtk.RESPONSE_CANCEL. Le traitement principal sera de prendre en compte les informations sur le vin retournées par le dialogue, si l'utilisateur a cliqué sur le bouton Ok.

Vous constaterez aussi que nous récupérons les widgets GTKEntry du dialogue pour lire ou modifier leurs textes. Cette fonction reste fort simple.

II-D - Tree Views et List Stores

Maintenant que nous avons les caractéristiques du vin de l'utilisateur, nous devons l'ajouter à la liste, dans le gtk.TreeView.

La caractéristique principale des GTKTreeViews est qu'ils affichent leurs données de la manière dont leur modèle leur dit de les afficher. Ils peuvent utiliser un gtk.ListStore, un gtk.TreeStore, un gtk.TreeModelSort, ou un gtk.GenericTreeModel. Dans cette exemple, nous utiliserons le gtk.ListStore.

La relation entre le Tree View et son modèle est un peu compliqué, mais une fois qu'on vous en aurez utilisé un, vous comprendrez pourquoi ils ont fait ça comme ça. En simplifiant outrageusement, le modèle représente les données, et le Tree View est une façon de les afficher. Ainsi, vous pouvez avoir de multiples façons d'afficher les mêmes données (modèle). On peut lire, dans le manuel de référence Gtk+ :

Pour créer une arborescente ou une liste en GTK+, utilisez l'interface GtkTreeModel avec le widget GtkTreeView. Ce widget est conçu en Modèle/Vue/Contrôleur et comprend quatre couches : Le widget de vue arborescente (GtkTreeView) La vue en colonne (GtkTreeViewColumn) Les afficheurs de cellules (GtkCellRenderer etc.) L'interface modèle (GtkTreeModel)

La vue est composée des trois premiers objets, tandis que le dernier est le Model. Le premier bénéfice que l'on retire de la conception MVC, c'est que plusieurs vues peuvent être créées pour un même modèle. Par example, un modèle basé sur le système de fichier peut être créé pour un gestionnaire de fichiers. Beaucoup de vues différentes peuvent être créées pour afficher différentes parties du système de fichiers, mais une seule devra être conservée en mémoire.

Le première chose que nous avons besoin de faire est d'ajouter un peut de code dans la méthode init de la classe pyWine, juste l'endroit où l'on connecte le dictionnaire à l'arborescence des widgets.

#Here are some variables that can be reused later
self.cWine = 0
self.cWinery = 1
self.cGrape = 2
self.cYear = 3

self.sWine = "Wine"
self.sWinery = "Winery"
self.sGrape = "Grape"
self.sYear = "Year"

#Get the treeView from the widget Tree
self.wineView = self.wTree.get_widget("wineView")
#Add all of the List Columns to the wineView
self.AddWineListColumn(self.sWine, self.cWine)
self.AddWineListColumn(self.sWinery, self.cWinery)
self.AddWineListColumn(self.sGrape, self.cGrape)
self.AddWineListColumn(self.sYear, self.cYear)

Ce code se lit d'une traite. Premièrement, nous créeons quelques variables qui nous servirons plus tard à faciliter les changements. Ensuite nous récupérons notre gtk.TreeView depuis l'arborescence des widgets. Après cela nous appelons une nouvelle fonction pour ajouter les colonnes nécessaires à la liste. AddWineListColumn est une petite fonction qui nous évitera de dupliquer du code à chaque création d'une colonne :

def AddWineListColumn(self, title, columnId):
"""This function adds a column to the list view.
First it create the gtk.TreeViewColumn and then set
some needed properties"""

column = gtk.TreeViewColumn(title, gtk.CellRendererText()
, text=columnId)
column.set_resizable(True)
column.set_sort_column_id(columnId)
self.wineView.append_column(column)

Ce code est un peu plus compliqué, tout d'abord, nous créeons une nouvelle gtk.TreeViewColumn qui utilise un afficheur de texte gtk.CellRendererText en tant qu'afficheur de cellules gtk.CellRenderer. Voici une information plus générale en provenance du manuel de référence GTK+ :

Une fois que le widget GtkTreeView dispose d'un modèle, il a besoin de savoir comment afficher ce dernier. Il fait cela avec des colonnes et des afficheurs de cellules.

Les afficheurs de cellules sont utilisés pour dessiner la donnée du modèle d'une certaine manière. Il existe un certain nombre d'afficheurs de cellules dans GTK+ 2.x, incluant GtkCellRendererText, GtkCellRendererPixbuf et GtkCellRendererToggle. Il est relativement facile d'écrire son propre afficheur de cellules.

Un GtkTreeViewColumn est l'objet qu'utilise GtkTreeView pour organiser les colonnes verticalement dans l'affichage arborescent. Il a besoin de connaître le nom de la colonne à afficher à l'utilisateur, quel type d'afficheur de cellules utiliser, et quel partie des données il faut lire depuis le modèle pour une rangée déterminée.

Ce que nous allons faire est donc de créer un colonne avec son titre, indiquer qu'elle utilisera un gtk.CellRendererText (pour afficher du texte simple), et lui donner l'élément du modèle auquel elle sera attachée. Nous la rendons ensuite redimensionnable, et permettons à l'utilisateur de trier par un clic sur l'entête de la colonne. Finalement, nous ajoutons la colonne au Tree View.

Maintenant que le visuel est fait, nous devons créer notre Model. Ceci sera fait dans la méthode init de la classe pyWine :

#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(str, str, str, str)
#Attatch the model to the treeView
self.wineView.set_model(self.wineList)

Nous créeons simplement un gtk.ListStore avec quatres éléments (NdT: qui correspondent aux colonnes à afficher) de type chaîne de caractères. Enfin, nous lions le modèle à la vue et c'est tout ce qu'il y a à faire pour notre gtk.TreeView.

II-E - Rassembler les pièces du puzzle

Pour boucler notre projet, il nous reste à écrire la fonction OnAddWine (appelée depuis le menu ou la barre d'outils) de la clase pyWine. C'est une fonction assez simple :

def OnAddWine(self, widget):
"""Called when the use wants to add a wine"""
#Create the dialog, show it, and store the results
wineDlg = wineDialog();
result,newWine = wineDlg.run()

if (result == gtk.RESPONSE_OK):
"""The user clicked Ok, so let's add this
wine to the wine list"""
self.wineList.append(newWine.getList())

Nous instancions notre dialogue wineDialog et l'exécutons pour ensuite mémoriser son résultat et les informations sur le vin que l'utilisateur aura saisit. Nous testons le résultat, qui doit être gtk.RESPONSE_OK (l'utilisateur ayant cliqué sur le bouton Ok) et alors nous ajoutons le vin dans notre gtk.ListStore. Ce dernier sera automatiquement affiché dans le gtk.TreeView car nous les avons connecté ensemble.

Nous utilisons une fontion getList() dans la classe pour faciliter la lecture du code source :

def getList(self):
"""This function returns a list made up of the
wine information.  It is used to add a wine to the
wineList easily"""
return [self.wine, self.winery, self.grape, self.year]

Notre application est terminée, pour peu que l'on accepte qu'elle ne sauvegarde aucune information. Elle représente bien les étapes nécessaires à la construction d'application complète en PyGTK.

Le source complet et le fichier glade peuvent être trouvés ici. Vous pouvez aussi lire le code source ci-dessous :

#!/usr/bin/env python

import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gtk
import gtk.glade
except:
sys.exit(1)

class pyWine:
"""This is an PyWine application"""

def __init__(self):

#Set the Glade file
self.gladefile = "pywine.glade"
self.wTree = gtk.glade.XML(self.gladefile, "mainWindow")

#Create our dictionay and connect it
dic = {"on_mainWindow_destroy" : gtk.main_quit
, "on_AddWine" : self.OnAddWine}
self.wTree.signal_autoconnect(dic)

#Here are some variables that can be reused later
self.cWine = 0
self.cWinery = 1
self.cGrape = 2
self.cYear = 3

self.sWine = "Wine"
self.sWinery = "Winery"
self.sGrape = "Grape"
self.sYear = "Year"

#Get the treeView from the widget Tree
self.wineView = self.wTree.get_widget("wineView")
#Add all of the List Columns to the wineView
self.AddWineListColumn(self.sWine, self.cWine)
self.AddWineListColumn(self.sWinery, self.cWinery)
self.AddWineListColumn(self.sGrape, self.cGrape)
self.AddWineListColumn(self.sYear, self.cYear)

#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(str, str, str, str)
#Attache the model to the treeView
self.wineView.set_model(self.wineList)

def AddWineListColumn(self, title, columnId):
"""This function adds a column to the list view.
First it create the gtk.TreeViewColumn and then set
some needed properties"""

column = gtk.TreeViewColumn(title, gtk.CellRendererText()
, text=columnId)
column.set_resizable(True)
column.set_sort_column_id(columnId)
self.wineView.append_column(column)

def OnAddWine(self, widget):
"""Called when the use wants to add a wine"""
#Cteate the dialog, show it, and store the results
wineDlg = wineDialog();
result,newWine = wineDlg.run()

if (result == gtk.RESPONSE_OK):
"""The user clicked Ok, so let's add this
wine to the wine list"""
self.wineList.append(newWine.getList())

class wineDialog:
"""This class is used to show wineDlg"""

def __init__(self, wine="", winery="", grape="", year=""):

#setup the glade file
self.gladefile = "pywine.glade"
#setup the wine that we will return
self.wine = Wine(wine,winery,grape,year)

def run(self):
"""This function will show the wineDlg"""

#load the dialog from the glade file
self.wTree = gtk.glade.XML(self.gladefile, "wineDlg")
#Get the actual dialog widget
self.dlg = self.wTree.get_widget("wineDlg")
#Get all of the Entry Widgets and set their text
self.enWine = self.wTree.get_widget("enWine")
self.enWine.set_text(self.wine.wine)
self.enWinery = self.wTree.get_widget("enWinery")
self.enWinery.set_text(self.wine.winery)
self.enGrape = self.wTree.get_widget("enGrape")
self.enGrape.set_text(self.wine.grape)
self.enYear = self.wTree.get_widget("enYear")
self.enYear.set_text(self.wine.year)

#run the dialog and store the response
self.result = self.dlg.run()
#get the value of the entry fields
self.wine.wine = self.enWine.get_text()
self.wine.winery = self.enWinery.get_text()
self.wine.grape = self.enGrape.get_text()
self.wine.year = self.enYear.get_text()

#we are done with the dialog, destory it
self.dlg.destroy()

#return the result and the wine
return self.result,self.wine

class Wine:
"""This class represents all the wine information"""

def __init__(self, wine="", winery="", grape="", year=""):

self.wine = wine
self.winery = winery
self.grape = grape
self.year = year

def getList(self):
"""This function returns a list made up of the
wine information.  It is used to add a wine to the
wineList easily"""
return [self.wine, self.winery, self.grape, self.year]

if __name__ == "__main__":
wine = pyWine()
gtk.main()

III - Améliorer notre application PyGTK

Où il sera décidé d'enrichir l'expérience utilisateur en terminant l'application PyWine.

Ce billet est la traduction, librement adaptée, du billet anglophone Extending our PyGTK Application.

Améliorer notre application PyGTK - Chap.3

Dans ce tutoriel, nous allons améliorer notre application PyWine pour rendre fonctionnelle l'édition des éléments ajoutés dans la liste, ainsi que le chargement et la sauvegarde de la liste des vins.

Le source complet du tutoriel peut être téléchargé ici.

Si l'application PyWine ne vous est pas familière ou que vous ne connaissez pas Glad et PyGTK, je vous suggère de lire les deux premiers tutoriels sur le sujet :

- Construire une application avec PyGTK et Glade

- Créer des interfaces graphique avec PyGTK et Glade

III-A - L'interface graphique - Glade

La première chose que nous allons faire, c'est d'ouvrir notre projet glade PyWine. Nous y ajoutons alors un bouton "Edit" dans sa barre d'outils :

1. Il faut créer la place nécessaire dans la barre d'outils. Sélectionnons là et passons la valeur de sa propriété Size à 2.

2. Ajoutons un Toolbar Button dans l'espace vide ainsi créé.

3. Appelons ce bouton btnEditWine, modifions son libellé pour "Edit wine", et son icône pour un stock icon Edit. Ensuite, ajoutons un gestionnaire sur l'événement clicked.

4. Nous allons un peu modifier le menu. Au lieu d'avoir un menu Add -> Wine, nous aurons Wine -> Add et Wine -> Edit. Il suffit de faire comme nous l'avons vu dans le précédent tutoriel et de créer le gestionnaire d'événement pour Wine -> Edit clicked rattaché à la même fonction que celle du clic sur le bouton btnEditWine.

III-B - Le code source

Maintenant, il faut développer le traitement effectué par le bouton Edit. Il faut d'abord récupérer le vin qui est sélectionné dans le gtk.TreeView. Il y a deux façons de le faire. Soit nous lisons toutes les données affichées par les colonnes, à la recherche des informations sélectionnées; soit nous stockons l'objet Wine dans le gtk.ListStore (notre modèle) mais nous ne l'afficherons pas dans le gtk.TreeView.

Cette dernière approche est plus simple et plus utile pour la suite, si notre classe Wine doit contenir de nouvelles informations, ou si nous laissons l'utilisateur libre de modifier les colonnes du gtk.TreeView. Il nous faut donc changer un peu le code.

D'abord, ajoutons la nouvelle colonne à la liste des variables les identifiant dans la mèthode init de pyWine :

self.cWineObject = 0
self.cWine = 1
self.cWinery = 2
self.cGrape = 3
self.cYear = 4

Notez que nous plaçons l'objet wine en position 0 de la liste, il faut alors modifier le code de création du modèle gtk.ListStore :

#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(gobject.TYPE_PYOBJECT
, gobject.TYPE_STRING
, gobject.TYPE_STRING
, gobject.TYPE_STRING
, gobject.TYPE_STRING)

La seule chose qui change est que le premier élément du gtk.ListStore est maintenant un objet python. Pour que ce code compile, il faut importer le module adéquat, au début du fichier source :

import gobject

La prochaine étape consiste à modifier la manière d'ajouter un vin au gtk.ListStore afin d'inclure l'objet Wine. Heureusement, dans notre précédent tutoriel, nous avions ajouté une méthode getList() à la classe Wine. Elle retourne la liste à ajouter au gtk.ListStore, il suffit alors d'éditer ceci :

def getList(self):
"""This function returns a list made up of the
wine information.  It is used to add a wine to the
wineList easily"""
return [self, self.wine, self.winery, self.grape, self.year]

C'est une modification mineure, nous avons simplement fait en sorte que getList() retourne l'objet Wine en tête de liste.

Pour la suite, nous allons permettre à l'utilisateur de modifier un vin mais avant, un changement doit être fait. Le premier tutoriel utilisait une mèthode init qui acceptait en paramètres les attributs d'un vin :

def __init__(self, wine="", winery="", grape="", year=""):

C'est très bien s'il y a peu de paramètres, mais si la classe Wine évolue, l'initialisation par init va devenir pénible. Nous allons donc transmettre une classe Wine :

def __init__(self, wine=None):
"""Initialize the class.
wine - a Wine object"""

#setup the glade file
self.gladefile = "pywine.glade"
#setup the wine that we will return
if (wine):
#They have passed a wine object
self.wine = wine
else:
#Just use a blank wine
self.wine = Wine()

Ensuite, il faut gérer la saisie du vin, ce qui sera fait par une fonction appelée on_EditWine(). Cette méthode est associée à l'événement clicked du bouton Edit Wine, et à l'option de menu Wine->Edit.

def on_EditWine(self, widget):
"""Called when the user wants to edit a wine entry"""

# Get the selection in the gtk.TreeView
selection = self.wineView.get_selection()
# Get the selection iter
model, selection_iter = selection.get_selected()

if (selection_iter):
"""There is a selection, so now get the the value at column
self.cWineObject, the Wine Object"""
wine = self.wineList.get_value(selection_iter, self.cWineObject)
# Create the wine dialog, based off of the current selection
wineDlg = wineDialog(wine);
result,newWine = wineDlg.run()

if (result == gtk.RESPONSE_OK):
"""The user clicked Ok, so let's save the changes back
into the gtk.ListStore"""
self.wineList.set(selection_iter
, self.cWineObject, newWine
, self.cWine, newWine.wine
, self.cWinery, newWine.winery
, self.cGrape, newWine.grape
, self.cYear, newWine.year)

La première ligne de la méthode est un appel à gtk.TreeView.get_selection() pour récupérer l'objet gtk.TreeSelection associé au gtk.TreeView. Nous récupérons alors le modèle gtk.TreeModel avec un appel à gtk.TreeSelection.get_selected() ainsi que le noeud sélectionné désigné par gtk.TreeIter. C'est ce noeud qui nous intéresse.

Si rien n'est sélectionné, alors get_selected() retourne None, sinon, nous utilisons le gtk.TreeIter pour obtenir l'objet Vin sélectionné par l'appel à gtk.TreeModel.get_value(). Avec l'objet Vin le reste du traitement est simple, nous créons l'objet wineDialog, l'affichons, et si on a cliqué sur son bouton OK, nous mettons à jour l'élément sélectionné dans le gtk.TreeView par l'appel à la fonction gtk.ListStore.set().

La fonction gtk.ListStore.set() est intéressante à plus d'un titre. Elle prend un premier paramètre gtk.TreeIter (la position pour mettre à jour les valeurs) et les paramètres suivants sont des paires (numéro de colonne, nouvelle valeur) ! Ma seule déception a étéait de ne pas trouver une fonction qui utilise une liste de la même manière que la fonction gtk.ListStore.append().

C'est tout pour la mdofication d'un vin ! Comme nous ne voulons pas avoir à tout re-saisir à chaque fois que nous démarrons l'application, il est temps de s'occuper de la sauvegarde et du chargement de notre liste de vins.

III-C - Enregistrer et charger la liste des vins

Empruntant tout d'abord deux fonctions utiltaires à l'outil de blogging offline WordPy :

def show_error_dlg(self, error_string):
"""This Function is used to show an error dialog when
an error occurs.
error_string - The error string that will be displayed
on the dialog.
"""
error_dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR
, message_format=error_string
, buttons=gtk.BUTTONS_OK)
error_dlg.run()
error_dlg.destroy()

Cette fonction offre une facilité pour avertir l'utilisateur qu'une erreur est survenue, grâce à un dialogue d'erreur. Nous l'ajouterons à la classe pyWine. Pour le détail de son fonctionnement, référez vous au tutorial sur l'outils de blogging offline WordPy.

Nous allons aussi récupérer la fonction browse_for_image() :

def browse_for_image(self):
"""This function is used to browse for an image.
The path to the image will be returned if the user
selects one, however a blank string will be returned
if they cancel or do not select one."""

file_open = gtk.FileChooserDialog(title="Select Image"
, action=gtk.FILE_CHOOSER_ACTION_OPEN
, buttons=(gtk.STOCK_CANCEL
, gtk.RESPONSE_CANCEL
, gtk.STOCK_OPEN
, gtk.RESPONSE_OK))
"""Create and add the Images filter"""
filter = gtk.FileFilter()
filter.set_name("Images")
filter.add_mime_type("image/png")
filter.add_mime_type("image/jpeg")
filter.add_mime_type("image/gif")
filter.add_pattern("*.png")
filter.add_pattern("*.jpg")
filter.add_pattern("*.gif")
file_open.add_filter(filter)
"""Create and add the 'all files' filter"""
filter = gtk.FileFilter()
filter.set_name("All files")
filter.add_pattern("*")
file_open.add_filter(filter)

"""Init the return value"""
result = ""
if file_open.run() == gtk.RESPONSE_OK:
result = file_open.get_filename()
file_open.destroy()

return result

Toutefois, nous la modifions pour qu'elle permette les ouvertures et sauvegarde de fichiers, et qu'elle reconnaisse les fichiers pyWine (*.pwi) au lieu des images. Un paramètre supplémentaire lui sera transmis afin de la faire se comporter différement selon que l'on souhaite ouvrir ou enregistrer un fichier. Ce sera la valeur que l'on affectera à la propriété action du gtk.FileChooserDialog :

def file_browse(self, dialog_action, file_name=""):
"""This function is used to browse for a pyWine file.
It can be either a save or open dialog depending on
what dialog_action is.
The path to the file will be returned if the user
selects one, however a blank string will be returned
if they cancel or do not select one.
dialog_action - The open or save mode for the dialog either
gtk.FILE_CHOOSER_ACTION_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE
file_name - Default name when doing a save"""

if (dialog_action==gtk.FILE_CHOOSER_ACTION_OPEN):
dialog_buttons = (gtk.STOCK_CANCEL
, gtk.RESPONSE_CANCEL
, gtk.STOCK_OPEN
, gtk.RESPONSE_OK)
else:
dialog_buttons = (gtk.STOCK_CANCEL
, gtk.RESPONSE_CANCEL
, gtk.STOCK_SAVE
, gtk.RESPONSE_OK)

file_dialog = gtk.FileChooserDialog(title="Select Project"
, action=dialog_action
, buttons=dialog_buttons)
"""set the filename if we are saving"""
if (dialog_action==gtk.FILE_CHOOSER_ACTION_SAVE):
file_dialog.set_current_name(file_name)
"""Create and add the pywine filter"""
filter = gtk.FileFilter()
filter.set_name("pyWine database")
filter.add_pattern("*." + FILE_EXT)
file_dialog.add_filter(filter)
"""Create and add the 'all files' filter"""
filter = gtk.FileFilter()
filter.set_name("All files")
filter.add_pattern("*")
file_dialog.add_filter(filter)

"""Init the return value"""
result = ""
if file_dialog.run() == gtk.RESPONSE_OK:
result = file_dialog.get_filename()
file_dialog.destroy()

return result

FILE_EXT est défini comme suit :

FILE_EXT = "pwi"

Dans le projet glade, nous ajoutons aussi les callbacks pour les options de menus File->Open et File->Save, de la même manière que nous l'avions fait pour Wine->Add et Wine->Edit. Je les ai appelé on_file_open et on_file_save :

#Create our dictionary and connect it
dic = {"on_mainWindow_destroy" : self.on_Quit
, "on_AddWine" : self.OnAddWine
, "on_EditWine" : self.on_EditWine
, "on_file_open" : self.on_file_open
, "on_file_save" : self.on_file_save}
self.wTree.signal_autoconnect(dic)

Pour la l'enregistrement et la lecture des objets Wine, nous utiliserons le module shelve de Python. C'est un module standard qui permet de sérialiser dans une base de données la plupart (sinon tous) des objets Python. Une autre approche aurait pu être de passer par l'utilisation de fichier XML, ou même d'utiliser le module pickle directement. Mais je pense que shelve a plus de sens pour notre application et qu'il reste plus abordable que la manipulation du XML.

La documentation nous éclaire :

Un shelf est un objet persistent organisé comme un dictionnaire. La différence avec les bases de données dbm est que les valeurs (pas les clés !) peuvent être n'importe quel objet Python - en fait, tous ce que le module pickle est capable de manipuler. Ceci inclue la plupart des instances de classes, les types données récursifs, et les objets contenant nombre d'autres sous-objets partagés. Les clés sont des chaînes de caractères (string) ordinaires.

III-C-1 - Enregistrer

Bien, nous allons maintenant écrire la méthode on_file_save(). Tout d'abord, l'utilisateur va choisir un emplacement pour y enregistrer son fichier, il décidera aussi du nom du fichier. Ensuite, nous nous assurerons que l'extension du fichier est celle que nous gérons puis nous parcourerons l'ensemble de éléments de gtk.TreeView et enregistrerons chaque objet Wine grâce au module shelve :

def on_file_save(self, widget):
"""Called when the user wants to save a wine list"""

# Get the File Save path
save_file = self.file_browse(gtk.FILE_CHOOSER_ACTION_SAVE, self.project_file)
if (save_file != ""):
# We have a path, ensure the proper extension
save_file, extension = os.path.splitext(save_file)
save_file = save_file + "." + FILE_EXT
""" Now we have the "real" file save loction create
the shelve file, use "n" to create a new file"""
db = shelve.open(save_file,"n")
"""Get the first item in the gtk.ListStore, and while it is not
None, move forward through the list saving each item"""
# Get the first item in the list
iter = self.wineList.get_iter_root()
while (iter):
# Get the wine at the current gtk.TreeIter
wine = self.wineList.get_value(iter, self.cWineObject)
# Use the iters position in the list as the key name
db[self.wineList.get_string_from_iter(iter)] = wine
# Get the next iter
iter = self.wineList.iter_next(iter)
#close the database and write changes to disk, we are done
db.close();
#set the project file
root, self.project_file = os.path.split(save_file)

Après le travail fait plus haut sur les objets gtk.TreeIter, ce code ne devrait pas être difficile à comprendre. En fait, La seule difficulté est la suivante, le reste étant plutôt bien commenté :

while (iter):
# Get the wine at the current gtk.TreeIter
wine = self.wineList.get_value(iter, self.cWineObject)
# Use the iters position in the list as the key name
db[self.wineList.get_string_from_iter(iter)] = wine
# Get the next iter
iter = self.wineList.iter_next(iter)

Nous effectuons une boucle parcourant chaque élément de gtk.ListStore et cela affecte l'objet Wine courant dans le fichier shelve avec la clé correspondant à la position de gtk.TreeIter.

db[self.wineList.get_string_from_iter(iter)] = wine

La fonction gtk.TreeModel.get_string_from_iter() retourne une représentation du chemin pointé par iter sous forme de chaîne de caractères. Cette chaîne est une liste de nombre séparés par deux-points. Par exemple, la chaîne "4:10:0:3" peut nous être retournée. Comme nous utilisons un gtk.ListStore, les valeurs retournées s'incrémentent lorsque nous avançons dans la liste.

Ainsi, le premier élément sera "0", le second "1", le troisième "2", et ainsi de suite. Ceci sera utile pour le chargement du fichier, puisque les clés des fichiers shelve ne sont pas garanties d'être présentées dans l'ordre.

C'est à la fermeture du fichier shelve que les données sont écrites sur le disque.

Remarquez l'utilisation de l'élément self.project_file comme nom de fichier par défaut, c'est un ajout à la classe. Il désigne le nom du projet courant et nous permet d'avoir un nom par défaut dans gtk.FileChooserDialog, quand nous enregistrons. La définition se trouve dans la fonction d'initialisation :

self.project_file = ""

Ceci nous affiche le dialogue :

III-D - Charger

Procédons maintenant à l'implémentation de la méthode on_file_open(). Si vous avez compris l'implémentation de on_file_save(), alors ce ne sera pas bien difficile :

def on_file_open(self, widget):
"""Called when the user wants to open a wine"""

# Get the file to open
open_file = self.file_browse(gtk.FILE_CHOOSER_ACTION_OPEN)
if (open_file != ""):
# We have a path, open it for reading
try:
db = shelve.open(open_file,"r")
if (db):
# We have opened the file, so empty out our gtk.TreeView
self.wineList.clear()
""" Since the shelve file is not gaurenteed to be in order we
move through the file starting at iter 0 and moving our
way up"""
count = 0;
while db.has_key(str(count)):
newwine = db[str(count)]
self.wineList.append(newwine.getList())
count = count +1
db.close();
#set the project file
root, self.project_file = os.path.split(open_file)
else:
self.show_error_dlg("Error opening file")
except:
self.show_error_dlg("Error opening file")

Remarquez que nous utilisons un compteur (count) et la fonction has_key() quand nous chargeons les éléments. Comme expliqué précédemment, nous enregistrons chaque objet Wine avec son chemin gtk.TreeIter, qui est un nombre car nous utilisons un gtk.ListStore. Mais l'ordre de lecture n'est pas garanti, il nous faut utiliser notre propre compteur pour lire chaque élément du fichier afin de commencer à zéro et incrémenter jusqu'à ce que l'on ne trouve plus la clé compteur dans le fichier. Nous convertissons les entiers en chaînes de caractères car les clés sont des chaînes.

Pour charger un objet Wine depuis le fichier, nous demandons simplement l'élément correspondant à la clé en cours :

newwine = db[str(count)]

Il suffit alors de l'ajouter à la liste des vins, et nous aurons chargé un fichier .pwi. Le code try except se charge de récupérer toute erreur pouvant survenir si nous ouvrons un fichier qui ne serait pas du format d'un projet pyWine.

III-E - Conclusion

C'est tout pour ce tutoriel, mais si vous avez bien compris, vous pouvez embrayer sur l'implémentation de File->New, ou ajouter un bouton Delete dans la barre d'outils, ou encore modifier le titre de l'application pour y écrire le nom du projet courant.

Vous pouvez même tenter la sauvegarde au format XML. Cela pourrait même devenir une fonctionnalités sympa qui permettrait à l'utilisateur de chosisir son format de fichier.

Le code complet de ce tutoriel est accessible sur le site d'origine de l'article.