Vous êtes sur la page 1sur 41

SÉ ÉDIT

Full Circle
LE MAGAZINE INDÉPENDANT DE LA COMMUNAUTÉ UBUNTU LINUX
RIE IO
PR N S
OG PÉ
ÉDITION SPÉCIALE SÉRIE PROGRAMMATION
RA CIA
MM LE
AT
IO
N

PROGRAMMER
EN PYTHON
Volume quatre
full circle magazine n'est affilié en aucune manière à Canonical Ltd
programmer en python volume 4
Au sujet du Full Circle Spécial Full Circle Magazine Nos coordonnées
Le Full Circle est un magazine gratuit,
libre et indépendant, consacré à toutes
les versions d'Ubuntu, qui fait partie des
systèmes d'exploitation Linux. Chaque
mois, nous publions des tutoriels, que
Full Circle
LE MAGAZINE INDÉPENDANT DE LA COMMUNAUTÉ UBUNTU LINUX
SiteWeb :
http://www.fullcirclemagazine.org/

Forums :
http://ubuntuforums.org/
forumdisplay.php?f=270
nous espérons utiles, et des articles pro-
posés par des lecteurs. Le Podcast, un
complément du Full Circle, parle du
Bienvenue dans une nouvelle édition spéciale IRC : #fullcirclemagazine on
chat.freenode.net
magazine même, mais aussi de tout ce consacrée à un seul sujet !
qui peut vous intéresser dans ce Équipe éditoriale :
domaine. Il ne s'agit de rien d'autre qu'une reprise de la série Programmer en Rédacteur en chef : Ronnie Tucker
Python, parties 22 à 26, numéros 48 à 52 ; pas de chichis, juste les faits. (pseudo : RonnieTucker)
Clause de non-responsabilité : ronnie@fullcirclemagazine.org
Gardez à l'esprit la date de publication ; les versions actuelles du matériel Webmaster : Rob Kerfia
Cette édition spéciale vous est fournie et des logiciels peuvent être différentes de celles illustrées. Il vous est (pseudo : admin / linuxgeekery-
sans aucune garantie ; les auteurs et le recommandé de bien vérifier la version de votre matériel et des logiciels admin@fullcirclemagazine.org
magazine Full Circle déclinent toute res- avant d'essayer d'émuler les tutoriels dans ces numéros spéciaux. Il se peut Podcast : Robin Catling
ponsabilité pour des pertes ou dom- que vous ayez des logiciels plus récents ou disponibles dans les dépôts de (pseudo : RobinCatling)
mages éventuels si des lecteurs choi- votre distribution. podcast@fullcirclemagazine.org
sissent d'en appliquer le contenu à Dir. comm. : Robert Clipsham
leurs ordinateur et matériel ou à ceux (pseudo : mrmonday) -
Amusez-vous !
mrmonday@fullcirclemagazine.org
des autres.

Les articles contenus dans ce magazine sont publiés sous la licence Creative Commons Attribution-Share Alike 3.0 Unported license. Cela signifie
que vous pouvez adapter, copier, distribuer et transmettre les articles mais uniquement sous les conditions suivantes : vous devez citer le nom de
l'auteur d'une certaine manière (au moins un nom, une adresse e-mail ou une URL) et le nom du magazine (« Full Circle Magazine ») ainsi que l'URL
www.fullcirclemagazine.org (sans pour autant suggérer qu'ils approuvent votre utilisation de l'œuvre). Si vous modifiez, transformez ou adaptez
cette création, vous devez distribuer la création qui en résulte sous la même licence ou une similaire.

Full Circle Magazine est entièrement indépendant de Canonical,


programmer en le sponsor desvolume
python projets 4Ubuntu. Vous ne devez en aucun cas présumer que les avis et les
opinions exprimés ici aient reçus l'approbation de Canonical.
TUTORIEL Programmer en Python - Partie 22
Greg Walters
##!/usr/bin/env python
d'accès complet. Il y a aussi une exten- import sys
CORRECTION sion qui peut être ajoutée avant cha- from mutagen.mp3 import MP3
try:
Dans la partie 21, il vous était dit de que entrée contenant la longueur de import pygtk
sauvegarder ce que vous aviez dans la chanson, le nom de l'album d'où pygtk.require("2.0")
except:
un fichier nommé « PlaylistMaker.gla- vient la chanson, le numéro de piste pass
de » alors que, dans le code, il etait et le nom du morceau. Nous allons try:
indiqué « playlistmaker.glade ». Je suis ignorer l'extension pour l'instant et import gtk
import gtk.glade
sûr que vous aviez remarqué que nous concentrer uniquement sur la except:
l'un contenait des majuscules et version de base. Voici un exemple sys.exit(1)
l'autre non. Le code s'exécutera seu- d'un fichier de liste de lecture M3U :
lement si il y a concordance entre le puis la définition de la classe
class CreateurListeDeLecture:
nom du fichier et l'appel, avec ou #EXTM3U
def __init__(self):
Adult Contemporary/Chris
sans majuscule. Rea/Collection/02 ­ On The
self.gladefile = "CreateurListeDeLecture.glade"
self.wTree = gtk.glade.XML(self.gladefile,"FenetrePrincipale")

P
Beach.mp3
Adult Contemporary/Chris
our bien commencer, vous Rea/Collection/07 ­ Fool (If et la routine principale
devez avoir les fichiers play- You Think It's Over).mp3
if __name__ == "__main__":
createurLDL = CreateurListeDeLecture()
listmaker.glade et playlistma- Adult Contemporary/Chris gtk.main()
ker.py du mois dernier. Si ce Rea/Collection/11 ­ Looking
n'est pas le cas, sautez sur le numéro
For The Summer.mp3 Ensuite, nous avons le dictionnaire qui devrait se trouver après la routine __init__.
def DicoEvenements(self):
précédent pour les récupérer. Avant
Tous les noms de chemins sont rela- dict = {"on_FenetrePrincipale_destroy": gtk.main_quit,
de passer au code, nous allons jeter "on_boBtnQuitter_clicked": gtk.main_quit,
tifs à l'emplacement du fichier de liste "on_boBtnAjouter_clicked": self.on_boBtnAjouter_clicked,
un œil à ce qu'est un fichier de liste
delecture. "on_boBtnSupprimer_clicked": self.on_boBtnSupprimer_clicked,
de lecture. Il y a plusieurs versions "on_boBtnEffacer_clicked": self.on_boBtnEffacer_clicked,
des listes de lecture, qui ont toutes "on_boBtnHaut_clicked": self.on_boBtnHaut_clicked,
Bien... Maintenant passons au code. "on_boBtnMonter_clicked": self.on_boBtnMonter_clicked,
des extensions différentes. Le fichier
Vous voyez à droite le début du code "on_boBtnDescendre_clicked": self.on_boBtnDescendre_clicked,
que nous allons créer sera de type "on_boBtnBas_clicked":self.on_boBtnBas_clicked,
source du mois dernier.
*.m3u. Dans sa forme la plus simple, "on_boBtnAPropos_clicked": self.on_boBtnAPropos_clicked,
"on_btnNomRepertoire_clicked": self.on_btnNomRepertoire_clicked,
c'est juste un fichier texte qui commence
Maintenant, nous devons créer une rou- "on_btnSauvegarderListe_clicked":
par « #EXTM3U » et qui contient une en- self.on_btnSauvegarderListe_clicked}
tine de gestion d'événement pour cha-
trée pour chaque fichier audio que self.wTree.signal_autoconnect(dict)
cun des événements que nous avons
vous voulez écouter - avec le chemin
programmer en python volume 4 3
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
mis en place. Notez que on_Fenetre- tion future. Ajoutez ceci après l'appel def on_boBtnAjouter_clicked(self,widget):
Principale_destroy et on_boBtnQuit- à DicoEvenements() dans la fonction pass
ter_clicked sont déjà faits pour nous, __i- nit__. def on_boBtnSupprimer_clicked(self,widget):
il n'en reste donc que dix autres à pass
def on_boBtnEffacer_clicked(self,widget):
écrire (voir en haut à droite). Écrivons self.CheminCourant = ""
pass
self.LigneCourante = 0
juste des ébauches pour l'instant. def on_boBtnHaut_clicked(self,widget):
self.NombreDeLignes = 0
pass
Nous modifierons ces ébauches de rou- def on_boBtnMonter_clicked(self,widget):
Maintenant, nous allons créer une fonc- pass
tines dans quelques minutes. Pour tion qui nous permet d'afficher une boî- def on_boBtnDescendre_clicked(self,widget):
l'instant, cela devrait nous permettre pass
te de dialogue à chaque fois que nous
de démarrer l'application ; nous pour- def on_boBtnBas_clicked(self,widget):
avons besoin de donner des informa- pass
rons tester les choses au fur et à me- tions à l'utilisateur. Il existe un ensem- def on_boBtnAPropos_clicked(self,widget):
sure que nous avançons. Nous devons ble de routines toutes faites que nous pass
quand même ajouter une ligne supplé- def on_btnNomRepertoire_clicked(self,widget):
allons utiliser, mais nous allons faire une
mentaire à la routine __init__ avant de routine à nous pour nous faciliter les
pass
pouvoir démarrer l'application. Après def on_btnSauvegarderListe_clicked(self,widget):
choses. C'est la routine gtk.Message- pass
la ligne self.wTree, ajouter : Dialogetlasyntaxeestlasuivante :
self.DicoEvenements() GTK_BUTTONS_NONE ­ aucun bouton
gtk.MessageDialog (parent, dra­ TONS_OK, "Ceci est un message
peaux, MessageType, boutons, de test ...")
Maintenant, vous pouvez exécuter l'ap- message) GTK_BUTTONS_OK ­ un bouton OK reponse = dlg.run ()
plication, voir la fenêtre, puis cliquer GTK_BUTTONS_CLOSE ­ un bouton dlg.destroy ()
Fermer
sur le bouton « Quitter de la barre d'ou- Une discussion est nécessaire avant GTK_BUTTONS_CANCEL ­ un bouton
tils » pour quitter l'application correcte- d'aller trop loin. Le type de message Annuler Toutefois, si vous voulez afficher une
ment. Enregistrez le code sous le nom peut être l'un des suivants : GTK_BUTTONS_YES_NO ­ boutons boîte de message plus d'une ou deux
« CreateurListeDeLecture-1a.py » et Oui et Non fois, c'est beaucoup de dactylographie.
GTK_BUTTONS_OK_CANCEL ­ boutons
essayez-le. Souvenez-vous qu'il faut GTK_MESSAGE_INFO ­ message
OK et Annuler La règle générale est que si vous écri-
d'information
l'enregistrer dans le même dossier que vez une série de lignes de code plus
GTK_MESSAGE_WARNING ­ message
le fichier glade que nous avons créé d'avertissement Normalement, vous utiliseriez le code d'une ou deux fois, il est généralement
la dernière fois ou bien copier le GTK_MESSAGE_QUESTION ­ question suivant, ou du code similaire, pour préférable de créer une fonction puis
fichier glade dans le dossier dans nécessitant un choix
créer la boîte de dialogue, l'afficher, at- de l'appeler. Pensez-y de cette manière :
GTK_MESSAGE_ERROR ­ message
lequel vous avez enregistré ce code. tendre une réponse, puis la détruire. si nous voulons afficher un message
d'erreur fatale
de dialogue pour l'utilisateur, disons
Nous avons également besoin de défi- dlg = gtk.MessageDialog (None, dix fois dans l'application, cela repré-
Et les types de boutons sont :
nir quelques variables pour une utilisa- 0, gtk.MESSAGE_INFO, gtk.BUT­ sente 10 x 3 (soit 30) lignes de code.

programmer en python volume 4 4


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
En faisant une fonction pour faire def MessageBox(self,niveau,texte):
cela pour nous (en utilisant l'exemple if niveau == "info":
que je viens de présenter), nous aurions dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,texte)
10 + 3 (soit 13) lignes de code à elif niveau == "warning":
dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_WARNING,gtk.BUTTONS_OK,texte)
écrire. Plus nous appelons une boîte elif niveau == "error":
de dialogue, moins cela fait de code dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_OK,texte)
à taper, et plus lisible est notre code. elif niveau == "question":
Notre fonction (page suivante en haut à dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO,texte)
if niveau == "question":
droite) nous permettra d'appeler l'un resp = dlg.run()
des quatre types de message de dlg.destroy()
dialogue avec une seule routine en return resp
utilisant différents paramètres. C'est else:
resp = dlg.run()
une fonction très simple que nous dlg.destroy()
pourrons ensuite appeler comme suit :
print "clic sur oui"
self.MessageBox("info", "Le def on_boBtnAjouter_clicked(self,widget):
bouton QUITTER a été cliqué") elif reponse == gtk.RESPONSE_NO: self.MessageBox("info","Clic sur bouton Ajouter...")
def on_boBtnSupprimer_clicked(self,widget):
print "clic sur non"
Notez que si nous choisissons d'utiliser self.MessageBox("info","Clic sur bouton Supprimer...")
def on_boBtnEffacer_clicked(self,widget):
le type de dialogue MESSAGE_QUES-
TION, il y a deux réponses possibles Vous voyez comment vous pouvez véri- self.MessageBox("info","Clic sur bouton Effacer...")
def on_boBtnHaut_clicked(self,widget):
qui seront retournées par la fenêtre fier la valeur du bouton cliqué. Alors self.MessageBox("info","Clic sur bouton Haut...")
de dialogue - un « oui » ou un « non ». maintenant, remplacez l'appel à « pass » def on_boBtnMonter_clicked(self,widget):
Quel que soit le bouton cliqué par dans chacune de nos routines de self.MessageBox("info","Clic sur bouton Monter...")
l'utilisateur, nous allons recevoir les gestion d'événement par ce que vous def on_boBtnDescendre_clicked(self,widget):
self.MessageBox("info","Clic sur bouton Descendre...")
informations de retour dans notre voyez ci-dessous à droite. def on_boBtnBas_clicked(self,widget):
code. Pour utiliser la boîte de dialogue self.MessageBox("info","Clic sur bouton Bas...")
de question, l'appel ressemblera à ceci : Nous n'allons pas le garder comme ça, def on_boBtnAPropos_clicked(self,widget):
mais cela vous donne une indication vi- self.MessageBox("info","Clic sur bouton À propos...")
def on_btnNomRepertoire_clicked(self,widget):
reponse = self.MessageBox(« ques­ suelle que les boutons fonctionnent self.MessageBox("info","Clic sur bouton NomRepertoire...")
tion », « Êtes­vous sûr de vou­ comme nous le voulons. Enregistrez def on_btnSauvegarderListe_clicked(self,widget):
loir faire cela maintenant ? ») maintenant le code sous « Créateur- self.MessageBox("info","Clic sur bouton SauvegarderListe...")
if reponse == gtk.RESPONSE_YES:
s'agit du widget treeview. Nous allons créer une fonction pour définir nos elle rendra notre code beaucoup
ListeDeLecture-1b.py » et testez votre références de widgets. Cette routine plus maniable et lisible. En fait, nous
programme. Maintenant nous allons va être appelée une seule fois, mais voulons créer des variables locales

programmer en python volume 4 5


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
qui font référence à des widgets dans def ReferencesWidgets(self):
la fenêtre glade - afin que nous puis- self.txtNomFicher = self.wTree.get_widget("txtNomFicher")
sions faire appel à eux chaque fois self.txtChemin = self.wTree.get_widget("txtChemin")
que (et si jamais) nous en avons self.boBtnAjouter = self.wTree.get_widget("boBtnAjouter")
self.boBtnSupprimer = self.wTree.get_widget("boBtnSupprimer")
besoin. Mettez cette fonction (page self.boBtnEffacer = self.wTree.get_widget("boBtnEffacer")
suivante en haut à droite) en dessous self.boBtnQuitter = self.wTree.get_widget("boBtnQuitter")
de la fonction DicoEvenements. self.boBtnAPropos = self.wTree.get_widget("boBtnAPropos")
self.boBtnHaut = self.wTree.get_widget("boBtnHaut")
self.boBtnMonter = self.wTree.get_widget("boBtnMonter")
Remarquez qu'il y a une chose qui n'est self.boBtnDescendre = self.wTree.get_widget("boBtnDescendre")
pas référencée dans notre routine. Il self.boBtnBas = self.wTree.get_widget("boBtnBas")
s'agit du widget treeview. Nous allons self.btnNomRepertoire = self.wTree.get_widget("btnNomRepertoire")
créer cette référence lorsque nous self.btnSauvegarderListe = self.wTree.get_widget("btnSauvegarderListe")
self.sbar = self.wTree.get_widget("statusbar3")
créerons l'arborescence elle-même. self.context_id = self.sbar.get_context_id("Statusbar")
Notez également la dernière ligne de
notre routine. Pour utiliser la barre puis ajoutez un appel à ceci juste après l'appel à self.DicoEvenements() dans la routine __init__.
d'état, il faut s'y référer par son id de
contexte. Nous allons utiliser cela self.ReferencesWidgets()
plus loin. Ensuite, nous allons mettre
en place la fonction qui affiche le
ceux que je considère être un en- def AfficherAPropos(self):
dialogue « à propos » quand on clique apropos = gtk.AboutDialog()
semble minimal.
sur le bouton À propos de la barre apropos.set_program_name("Createur de liste de
d'outils. Encore une fois, ceci est une lecture")
Avant de poursuivre, nous devons discu- apropos.set_version("1.0")
routine intégrée fournie par la biblio-
ter de ce qui se produira à partir d'ici. apropos.set_copyright("(c) 2011 by Greg Walters")
thèque GTK. Placez ceci après la apropos.set_comments("Ecrit pour le Full Circle
L'idée générale est que l'utilisateur
fonction MessageBox. Voici le code, Magazine")
clique sur le bouton « Ajouter » de la apropos.set_website("http://thedesignatedgeek.com")
en bas à droite. Sauvegardez votre
barre d'outils, nous afficherons alors apropos.run()
code, puis faites un essai. Vous devriez
une boîte de dialogue de fichier pour apropos.destroy()
voir une fenêtre pop-up, centrée dans
lui permettre d'ajouter des fichiers à
notre application, qui affiche ce que Maintenant, commentez (ou retirez simplement) l'appel à MessageBox dans la
la liste de lecture, puis nous afficherons
nous avons prévu. Il y a plusieurs attri- routine on_boBtnAPropos_clicked, et remplacez-le par un appel à la fonction
les informations du fichier dans notre
buts que vous pouvez définir pour la AfficherAPropos. Cela devrait ressembler à :
widget treeview. De là, il peut ajouter
boîteàpropos(quipeuvent êtretrouvéssur
d'autres fichiers, supprimer un fichier
http://www.pygtk.org/docs/pygtk/class- def on_boBtnAPropos_clicked(self,widget):
unique, supprimer tous les fichiers, #self.MessageBox("info","Clic sur bouton APropos...")
gtkaboutdialog.html), mais ceux-ci sont
déplacer un fichier vers le haut ou le self.AfficherAPropos()

programmer en python volume 4 6


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22

bas, ou bien tout en haut ou tout en lecture. Vous pourriez def SetupTreeview(self):
bas de l'arborescence. Enfin, il va définir bien sûr ajouter d'autres self.cNomFic = 0
le chemin où le fichier sera enregistré, colonnes si vous le souhai- self.cTypeFic = 1
fournir un nom de fichier avec une tez, mais pour l'instant nous self.cCheminFic = 2
self.sNomFic = "NomFichier"
extension « m3u », puis cliquer sur le allons nous contenter de self.sTypeFic = "Type"
bouton « Sauvegarder ». Bien que cela trois. Une arborescence self.sCheminFic = "Dossier"
semble assez simple, il se passe est simplement un conte- self.treeview = self.wTree.get_widget("treeview1")
beaucoup de choses en coulisses. La neur visuel de stockage self.AjouterColonne(self.sNomFic,self.cNomFic)
self.AjouterColonne(self.sTypeFic,self.cTypeFic)
magie se produit dans le widget qui détient et affiche self.AjouterColonne(self.sCheminFic,self.cCheminFic)
treeview, nous allons donc en discuter. un modèle. Le modèle self.listeLecture = gtk.ListStore(str,str,str)
Cela ira assez loin, alors lisez atten- est le véritable « dis- self.treeview.set_model(self.listeLecture)
tivement, car il faut le comprendre positif » qui contient et self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
pour éviter de commettre des erreurs manipule nos données. Il
plus tard. Une arborescence peut existe deux modèles prédéfinis qui l'arborescence de notre modèle. ajouter les colonnes, mettre en place
être quelque chose d'aussi simple sont utilisés avec un treeview, mais • Remplir les données. le ListStore, et définir le modèle. Voici
qu'une liste à colonnes de données, vous pouvez certainement créer le le code pour la fonction. Placez-le
comme dans une feuille de calcul ou vôtre. Cela étant dit, pour 98 % de La troisième étape consiste à mettre après la fonction ReferencesWidget.
une base de données, ou bien elle votre travail, l'un des deux modèles en place le type de moteur de rendu
peut être plus complexe, comme une prédéfinis fera ce dont vous avez que la colonne utilisera pour afficher Les variables cNomFic, cTypeFic et cChe-
liste de fichiers/dossiers avec des besoin. Les deux types sont GTKListStore les données. C'est tout simplement minFic définissent les numéros de co-
parents et enfants, où le dossier serait et GTKTreeStore. Comme leur nom une routine qui est utilisée pour tracer lonne. Les variables sNomFic, sTypeFic
le parent et les fichiers de ce dossier l'indique, le modèle ListStore est habi- les données dans le modèle de l'arbre. et sCheminFic contiennent les noms
seraient les enfants, ou quelque chose tuellement utilisé pour les listes, le GTK fournit de nombreux moteurs de colonnes de notre vue. La septième
d'encore plus complexe. Pour ce projet, TreeStore est utilisé pour les arbres. de rendu de cellules différents, mais ligne définit la variable de référence
nous allons utiliser le premier exem- Pour notre application, nous allons normalement vous utiliserez le plus du widget treeview tel qu'il figure
ple, une liste à colonnes. Dans la liste, utiliser un GTKListStore. Les étapes souvent GtkCellRenderText et GtkCell- dans notre fichier glade.
RendererToggle.
il y aura trois colonnes. Une pour le de base sont les suivantes :
nom du fichier de musique, une pour Ensuite nous appelons une routine
• Créer une référence au widget Nous allons donc créer une fonction (page suivante, en haut à droite), que
l'extension du fichier (mp3, ogg, wav,
TreeView. (ci-dessus) qui met en place notre nous allons créer dans un instant,
etc.) et la dernière colonne pour le
• Ajouter les colonnes. widget TreeView. Nous allons l'appeler pour chaque colonne que nous voulons.
chemin d'accès. En combinant tout ça
• Définir le type de moteur de rendu SetupTreeview. Nous allons d'abord Puis, nous définissons notre GTK-
dans une chaîne (chemin d'accès, nom
à utiliser. définir quelques variables pour nos ListStore avec trois champs de texte
de fichier, extension) on obtient l'entrée
• Créer le ListStore. colonnes, définir la variable de réfé- et, enfin, nous utilisons ce
que nous allons écrire dans la liste de
• Définir l'attribut de modèle dans rence du TreeView proprement dit, GTKListStore comme attribut de

programmer en python volume 4 7


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
modèle de notre widget TreeView. avons maintenant
def AjouterColonne(self,titre,idColonne):
Nous allons ensuite créer la fonction trois colonnes avec colonne = gtk.TreeViewColumn(titre,gtk.CellRendererText(),text=idColonne)
AjouterColonne. Placez-la après la en-têtesdansnotre colonne.set_resizable(True)
fonction SetupTreeview. widget TreeView. colonne.set_sort_column_id(idColonne)
self.treeview.append_column(colonne)
Chaque colonne est créée avec cette Il reste tellement
de fichiers. Nous pourrions coder ça class DialogueFichier:
fonction. Nous lui passons le titre de de choses à faire. Nous devons avoir
en dur simplement avec des lignes de def AfficheDialogue(self,
la colonne (ce qui est affiché sur la un moyen d'obtenir les noms de fi- type,CheminCourant):
code dans le gestionnaire d'événements
première ligne de chaque colonne) et chiers de musique de l'utilisateur et
on_boBtnAjouter_clicked, mais nous
un idColonne. Dans ce cas, nous utili- un moyen de les mettre dans le Tree- La première partie de notre code doit
allons faire une classe distincte pour
sons les variables que nous avons View sous forme de lignes de don- être une instruction IF
le gérer. Tant que nous y sommes,
créées plus tôt (sNomFic et cNomFic). nées. Nous devons créer nos fonctions
nous pouvons faire en sorte que cette
Nous créons ensuite une colonne Supprimer, Effacer tout, les fonctions if type == 0: # choix de fichier
classe gère non seulement un dialogue
dans notre widget TreeView donnant de déplacement, la routine de sauve- ...
Ouvrir un fichier, mais aussi un dia- else: # choix de dossier
le titre, le type de rendu de cellule et garde et les routines de chemins de
logue Sélectionner un dossier. Com- ...
enfin l'id de la colonne. Nous indi- fichiers, plus quelques « jolies » choses
me auparavant avec la fonction Mes-
quons ensuite que la colonne est re- qui donneront à notre application un Avant d'aller plus loin, nous allons voir
sageBox, vous pouvez l'extraire dans
dimensionnable, nous définissons l'id aspect plus professionnel. Commençons la façon dont la boîte de dialogue de
un fichier qui contient toutes sortes
de tri et ajoutons enfin la colonne par la routine « Ajouter ». Après tout, fichier/dossier est effectivement appe-
de routines réutilisables pour un usage
dans le TreeView. c'est le premier bouton sur notre lée et utilisée. La syntaxe de la boîte de
ultérieur.
barre d'outils. Lorsque l'utilisateur dialogue se présente comme suit :
Ajoutez ces deux fonctions à votre clique sur le bouton Ajouter, nous
Nous allons commencer par définir une
code. J'ai choisi de les mettre tout de voulons faire apparaître une fenêtre gtk.FileChooserDia­
nouvelle classe appelée DialogueFi-
suite après la fonction Referen- de dialogue « standard » d'ouverture log(titre,parent,action,bou­
chier qui a une seule fonction appelée
cesWidget, mais vous pouvez les mettre de fichier, qui permet des sélections tons,backend)
AfficheDialogue. Cette fonction prendra
n'importe où dans la classe CreateurLis- multiples. Une fois que l'utilisateur a
deux paramètres, l'un appelé « type » et retourne un objet fenêtre de dia-
teDeLecture. Ajoutez la ligne suivante fait son choix, nous voulons ensuite
(un '0' ou un '1'), qui précise si nous logue. Notre première ligne (dans le cas
après l'appel à ReferencesWidget() dans prendre ces données et les ajouter
créons un dialogue d'ouverture de où type vaut 0) sera la ligne ci-dessous.
la fonction __init__ pour appeler la dans l'arborescence, comme je l'ai indiqué
fichier ou de sélection de dossier, et
fonction : ci-dessus. Ainsi, la première chose
l'autre, qui est le chemin à utiliser Comme vous pouvez le voir, le titre
logique à faire est de travailler sur la
pour la vue par défaut de la boîte de est « Choisir les fichiers a ajouter… »,
self.SetupTreeview () boîte de dialogue Fichier. Encore une
dialogue appelée CheminCourant. Créez le parent est défini sur none (aucun).
fois, GTK nous fournit un moyen d'appe-
cette classe juste avant notre code Nous demandons une fenêtre de type
Enregistrez et exécutez votre pro- ler une boîte de dialogue « standard »
principal à la fin du fichier source. ouverture de fichier (action) et nous
gramme et vous verrez que nous

programmer en python volume 4 8


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
voulons des boutons « Annuler » et « un chemin, la valeur par défaut est le
Ouvrir », les deux utilisant des icônes de dossier où réside notre application. ou la retirer. Notez que lorsque nous touche [Ctrl] et cliquer sur plusieurs
type « stock ». Nous réglons également Ainsi, si les fichiers de musique que l'utili- sortons de la partie concernant le fichiers pour les sélectionner individuel-
les codes de retour de gtk.RESPON- sateur utilise sont dans /media/musi- bouton Ouvrir dans cette routine, nous lement, ou sur la touche [Maj] pour sé-
SE_CANCELetgtk.RESPONSE_OKlorsque que/ ils sont ensuite triés par genre renvoyons deux ensembles de valeurs : lectionner plusieurs fichiers contigus.
l'utilisateur fait ses choix. L'appel au puis par artiste, et puis après par selectionFichiers qui est une liste des Cliquez sur le bouton « Ouvrir », et
sélecteur de dossier dans la clause album. Supposons également que fichiers sélectionnés par l'utilisateur, examinez la réponse dans un termi-
else est similaire. l'utilisateur a installé notre application ainsi que le CheminCourant. nal. Remarquez que si vous cliquez
dans /home/user2/createurListeDeLec- sur le bouton « Annuler » à ce moment,
Fondamentalement, les seules cho- ture. Chaque fois que nous faisons Afin que la routine fasse quelque chose, vous obtiendrez un message d'erreur.
ses qui ont changé entre les deux apparaître le dialogue, le dossier de ajoutez la ligne suivante dans la rou- C'est parce que le code ci-dessus sup-
définitions sont le titre (ci-dessus à départ serait /home/user2/createurLis- tine on_boBtnAjouter_clicked : pose qu'il n'y a pas de fichiers sélec-
droite) et le type d'action. Donc le teDeLecture. Rapidement, l'utilisateur tionnés. Ne vous inquiétez pas pour
fd = DialogueFichier ()
code de la classe devrait maintenant devrait se sentir frustré par cela, pré- l'instant, nous allons régler cela sous
être le code affiché au milieu à droite. férant retrouver le dernier dossier fichiersChoisis,self.CheminCou­ peu. Je voulais simplement vous per-
dans lequel il était lorsqu'il démarre rant = fd.AfficheDia­ mettre de voir ce qui revient si l'on ap-
Nous définissons la réponse par dé- la prochaine fois. Vous comprenez ? logue(0,self.CheminCourant) puie sur le bouton « Ouvrir ». Une chose
faut à la touche OK, puis activons la Bien. Voici donc en bas à droite les que nous devrions faire est d'ajouter
fonctionnalité de sélection multiple lignes de code suivantes.Ici, nous véri- Ici on récupère les deux valeurs de un filtre à notre fenêtre d'ouverture
pour que l'utilisateur puisse sélectionner fions les réponses renvoyées. Si l'utili- retour qui sont renvoyées depuis le de fichier. Puisque nous attendons
(vous l'aurez deviné) plusieurs fichiers sateur a cliqué sur le bouton « Ouvrir » return. Pour le moment, ajoutez le que l'utilisateur sélectionne normalement
à ajouter. Si nous n'avions pas indiqué qui renvoie gtk.RES-PONSE_OK, nous code ci-dessous pour voir à quoi les des fichiers de musique, nous devrions :
cela, la boîte de dialogue permettrait obtenons le nom ou les noms des fi- informations retournées ressemblent : 1) donner la possibilité d'afficher des
seulement de sélectionner un fichier chiers que l'utilisateur a sélectionné, fichiers de musique uniquement et,
for f in fichiersChoisis:
à la fois, car set_select_multiple est on définit le chemin d'accès courant print "Choix utilisateur : 2) donner la possibilité d'afficher
réglé sur faux par défaut. Nos lignes vers le dossier où nous sommes, on %s" % f tous les fichiers au cas où. Nous
sui-vantes règlent le chemin actuel, détruit la boîte de dialogue, puis on faisons cela en utilisant les attributs
puis affichent la boîte de dialogue renvoie les données à la routine appe- print “Chemin courant : %s” % FileFilter de la boîte de dialogue.
self.CheminCourant
elle-même. Avant de taper le code, je lante. Si, en revanche, l'utilisateur a Voici le code pour cela, qu'il faut
vais vous expliquer pourquoi nous cliqué sur le bouton « Annuler », il suffit Lorsque vous exécutez le programme, placer dans la partie « type == 0 »
devons nous occuper du chemin courant. de détruire la boîte de dialogue. Je cliquez sur le bouton « Ajouter ». juste après la ligne créant le dialogue.
À chaque fois que vous faites appa- mets l'instruction print là juste pour Vous verrez la boîte de dialogue de
raître une boîte de dialogue de fi- vous montrer que l'appui sur le bouton fichier. Allez maintenant à un endroit
filtre = gtk.FileFilter()
filtre.set_name(“Fichiers musi­
chier et que vous ne définissez pas a fonctionné. Vous pouvez la laisser où vous avez des fichiers et sélection- caux”)
nez-les. Vous pouvez appuyer sur la filtre.add_pattern(“*.mp3”)
programmer en python volume 4 9
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 22
filtre.add_pattern(“*.ogg”) mentez les dernières lignes que nous def on_boBtnAjouter_clicked(self,widget):
filtre.add_pattern(“*.wav”)d avons ajoutées et remplacez-les par fd = DialogueFichier()
ialogue.add_filter(filtre) fichiersChoisis,self.CheminCourant = fd.AfficheDia­
filtre = cette seule ligne : logue(0,self.CheminCourant)
gtk.FileFilter()filtre.set_n self.AjouterFichiers(fichiersChoisis)
ame(“Tous les fichiers”) self.AjouterFichiers(fichiers­
filtre.add_pattern(“*”)dialo Choisis)
gue.add_filter(filtre)
Nous devons maintenant créer la fonction à laquelle nous venons de faire
Notre routine ressemble maintenant appel. Placez cette fonction après la routine on_btnSauvegarderListe_clicked.
Nous mettons en place deux « grou- au code affiché ci-contre.
def AjouterFichiers(self,ListeFichiers):
pes  », l'un pour les fichiers de musi-
compteur = 0
que (filtre.set_name(“Fichiers musicaux”)), Ainsi, lorsque nous aurons la réponse for f in ListeFichiers:
et l'autre pour tous les fichiers. Nous au retour de la fenêtre de sélection debutExt = f.rfind(".")
utilisons un motif pour définir les de fichiers, nous enverrons la liste debutnomFic = f.rfind("/")
extension = f[debutExt+1:]
types de fichiers que nous voulons. contenant les fichiers sélectionnés à nomFic = f[debutnomFic+1:debutExt]
J'ai défini trois motifs, mais vous cette routine. Une fois ici, nous créons cheminFic = f[:debutnomFic]
pouvez ajouter ou supprimer tous une variable de compteur (le nombre data = [nomFic,extension,cheminFic]
ceux que vous souhaitez. Je mets le de fichiers que nous ajoutons), puis self.listeLecture.append(data)
compteur += 1
filtre pour la musique en premier, analysons la liste. Rappelez-vous que self.NombreDeLignes += compteur
puisque c'est ce qui intéresse principa- chaque entrée contient le nom de self.sbar.push(self.context_id,"%s fichiers ajoutes
lement l'utilisateur. Ainsi, les étapes fichier complet avec le chemin et l'exten- sur un total de %d" % (compteur,self.NombreDeLignes))
sont : sion. Nous allons devoir fractionner
le nom du fichier en chemin, nom de ceci dans listeLecture. Nous incré- La prochaine fois, nous allons
• Définir une variable de filtre. fichier et extension. Nous récupé- mentons le compteur puisque nous finaliser notre application, en
• Régler le nom. rons d'abord le tout dernier « . » dans avons fait tout le travail. Enfin on remplissant les routines
• Ajouter un motif. le nom de fichier et supposons que incrémente la variable NombreDeLi- manquantes, etc.
• Ajouter le filtre à la boîte de dialo- c'est le début de l'extension, et nous gnes qui contient le nombre total de
gue. affectons sa position dans la chaîne à lignes dans listeLecture et nous affi-
debutExt. Nous trouvons ensuite le chons un message dans la barre d'état.
Vous pouvez avoir autant ou aussi tout dernier « / » dans le nom du
peu de filtres que vous le souhai- fichier pour déterminer le début du Maintenant vous pouvez lancer l'ap-
tez. Notez également qu'une fois que nom de fichier. Puis, nous découpons plication et voir les données dans
vous avez ajouté le filtre à la boîte la chaîne en extension, nom de fi- l'arborescence. Comme toujours, le
de dialogue, vous pouvez réutiliser chier et chemin du fichier. Nous pla- code complet peut être trouvé ici :
la variable de filtre. Retournez dans çons ensuite ces valeurs dans une
la routine on_boBtnAjouter_clicked, com- liste nommée « data » et ajoutons http://pastebin.com/wTCcGDSW.

programmer en python volume 4 10


TUTORIEL Programmer en Python - Partie 23
Greg Walters

C
ette fois-ci, nous allons ter- cipale, allez dans l'onglet Général et elif response == gtk.RESPONSE_CANCEL:
miner notre programme de descendez jusqu'à trouver Icône. En print 'Annulation, aucun fichier choisi'
création de liste de lecture. utilisant l'outil de parcours de dialog.destroy()
La dernière fois, nous avions fichiers, trouvez votre icône et sélec-
bien avancé, mais nous n'avons pas tionnez-la. Maintenant le champ de Remarquez que nous ne renvoyons rien. C'est ce qui causait l'erreur. Pour
terminé certaines parties. Nous ne pou- texte devrait contenir « logo.png ». Puis, réparer cela, ajoutez la ligne suivante après la ligne dialog.destroy() :
vons pas encore sauvegarder la liste dans la boîte de hiérarchie, choisissez Return ([],"")
de lecture, les fonctions de déplacement treeview1, allez dans l'onglet Si-
ne sont pas implémentées, nous ne gnaux et ajoutez un gestionnaire pour Ainsi il n'y aura plus d'erreur. Ensuite, ajoutons le gestionnaire d'événement
pouvons pas choisir le chemin vers on_treeview1_cur-sor_changed dans que nous avons créé dans glade pour le champ de texte. Dans notre
lequel on veut sauvegarder, etc. Cepen- la partie GtkTreeView | cursor- dictionnaire, ajoutez la ligne suivante :
dant, nous devons faire certaines cho- changed. Souvenez-vous que nous
ses avant de commencer à coder. avons vu le mois dernier que vous "on_txtNomFichierFilename_key_press_event":
Tout d'abord, nous devons trouver devez cliquer à côté pour conserver self.txtNomFichierKeyPress,
une image pour le logo de notre appli- vos modifications. Enfin, toujours
cation dans la boîte « À propos » et dans la boîte hiérarchie, choisissez Vous vous souvenez que cela crée une fonction pour gérer l'appui sur les
lorsque l'application est minimisée. Vous txtNomFichier et allez dans l'onglet touches du clavier. Créons maintenant la fonction :
pouvez chercher une icône qui vous Signaux. Descendez jusqu'à trouver
plaît dans le répertoire /usr/share/icons, GtkWidget et descendez encore def txtNomFichierKeyPress(self,widget,data):
if data.keyval == 65293: # valeur de la touche Entree
ou aller sur le web en chercher une jusqu'à key_press_event. Ajoutez un self.SauveListeLecture()
ou encore en créer une vous-même. gestionnaire d'événement pour
Quel que soit le choix, placez cette on_txtNomFichier_key_press_event. est modifier le code de la classe chaque touche enfoncée lorsque l'uti-
image dans le répertoire contenant Sauvegardez votre projet glade et DialogueFichier. Si vous vous souve- lisateur se trouve dans le champ de
le code source et le fichier glade du fermez glade. nez de la dernière fois, si l'utilisateur texte txtNomFichier et la compare à
mois dernier. Nommez-la logo.png. cliquait le bouton « Annuler », il se pro- la valeur 65293, qui est le code attri-
Ensuite, nous devons ouvrir le fichier Maintenant il est temps de terminer duisait une erreur. Nous allons com- bué à la touche Entrée. Si cela cor-
glade du mois dernier et faire quelques notre projet. Nous commencerons à mencer par corriger ça. À la fin de la respond, alors il appelle la fonction
changements. coder là où nous en étions restés le routine, vous avez le code ci-dessus. SauvegarderListe. L'utilisateur n'a même
mois dernier. pas besoin de cliquer sur le bouton.
Tout d'abord, avec la FenetrePrin- Comme vous pouvez le supposer,
La première chose que je veux faire cela regarde simplement la valeur de Maintenant passons au code. Occu-

programmer en python volume 4 11


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
pons-nous du bouton « Effacer » de la Je sais que vous pensez : « Mais bon def AjouterFichiers(self,ListeFichiers):
barre d'outils. Lorsque l'utilisateur cli- sang, qu'est-ce qu'un itérateur ? ». Eh compteur = 0
que sur ce bouton, on veut effacer la bien, vous en avez déjà utilisé sans for f in ListeFichiers:
debutExt = f.rfind(".")
liste arborescente et ListStore. Cela même le savoir. Regardez le code debutnomFic = f.rfind("/")
se fait en une ligne, que l'on peut suivant (ci-dessus à droite) provenant extension = f[debutExt+1:]
placer dans la routine on_boBtnEffa- de la fonction AjouterFichiers du mois nomFic = f[debutnomFic+1:debutExt]
cer_clicked. dernier. cheminFic = f[:debutnomFic]
data = [nomFic,extension,cheminFic]
self.listeLecture.append(data)
def Regardez la boucle for. On utilise un compteur += 1
on_boBtnEffacer_clicked(self
itérateur pour parcourir la liste
,widget) ::
ListeFichiers. Dans ce cas, l'itérateur def on_boBtnSupprimer_clicked(self,widget):
self.playList.clear() passe tout simplement d'une entrée sel = self.treeview.get_selection()
(modele,lignes) = sel.get_selected_rows() iter=[]
de la liste à la suivante, renvoyant for ligne in lignes:
Nous disons simplement à la liste de chaque élément séparément. Nous iter.append(self.listeLecture.get_iter(ligne))
lecture ListStore de s'effacer. C'était allons créer un itérateur, le remplir for i in iter:
facile. Maintenant occupons-nous du avec les lignes de la vue arbores- if i is not None:
self.listeLecture.remove(i)
bouton « Supprimer » de la barre d'outils. cente sélectionnées et l'utiliser comme self.NombreDeLignes ­= 1
C'est plus difficile, mais une fois ter- une liste. Voici donc le code (au self.sbar.push(self.context_id,"%d fichiers dans la
miné vous allez comprendre. milieu à droite) pour on_boBtnSup- liste." % (self.NombreDeLignes))
primer.
D'abord nous devons parler de la def on_btnNomRepertoire_clicked(self,widget):
fd = DialogueFichier()
façon dont nous récupérons une sé- La première ligne crée l'objet TreeSe-
cheminFichier,self.CheminCourant =
lection depuis la liste arborescente lection. On l'utilise pour récupérer les fd.AfficheDialogue(1,self.CheminCourant)
et ListStore. C'est un peu compliqué, lignes sélectionnées (il n'y en a qu'une self.txtChemin.set_text(cheminFichier[0])
alors allons doucement. Pour récu- car notre modèle n'est pas réglé pour
pérer des données depuis ListStore, offrir la sélection multiple), remplir tions de déplacement, occupons- par rapport à avant est la dernière
nous devons d'abord récupérer un une liste nommée iter avec, et la par- nous de la fonction de sauvegarde ligne de ce code. On place le nom du
objet gtk.TreeSelection qui nous ai- courir en enlevant chaque élément du chemin des fichiers. On utilisera chemin retourné par la fenêtre de
dera à gérer la sélection à l'intérieur (comme la méthode .clear). On décré- notre classe DialogueFichier comme dialogue dans le champ de texte que
d'un treeview. Ensuite, on utilise cet mente également la variable Nom- précédemment. On placera tout le code l'on a précédemment initialisé avec
objet pour récupérer le type de mo- breDeLignes, puis on affiche le nom- pour faire cela (en bas à droite) dans la la méthode set_text. Souvenez-vous
dèle et un itérateur qui contient les bre de fichiers dans la barre d'état. routineon_boBtnNomRepertoire_clicked. que les données nous sont renvoyées
lignes sélectionnées. sous forme de liste, même s'il n'y a
Maintenant, avant de passer aux fonc- La seule chose vraiment différente qu'un seul élément. C'est pourquoi

programmer en python volume 4 12


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
on utilise chemin[0]. def SauveListeLecture(self):
cf = self.txtChemin.get_text() # recuperer le chemin dans le champ de texte
nf = self.txtNomFichier.get_text() # recuperer le nom du fichier dans le champ de
Écrivons la fonction de sauve-garde texte
de fichier. On peut faire ça avant de
passer aux fonctions de dé- Maintenant on vérifie les valeurs :
placement. Nous allons créer une
fonc-tion SauvegarderListe. La if cf == "": # SI le chemin est vide
première chose à faire (ci-dessus à self.MessageBox("erreur","Veuillez fournir un chemin pour la liste de lecture.")
elif nf == "": # SI le nom de fichier est vide
droite) est de vérifier s'il y a quelque self.MessageBox("erreur","Veuillez fournir un nom pour le fichier liste de
chose dans le champ de texte lecture.")
txtChemin. Ensuite nous devons else: # Sinon, on peut continuer
vérifier s'il y a un nom de fichier dans
fic = open(cf + "/" + nf,"w") # ouvrir le fichier
le champ de texte txtNomFi-chier. fic.writelines('#EXTM3U\n') # afficher l'en­tete M3U
Pour ces deux valeurs, on utilise la for ligne in self.listeLecture:
méthode get_text() du champ de fic.writelines("%s/%s.%s\n" % (ligne[2],ligne[0],ligne[1])) # ecrit les donnees
texte. fic.close # referme le fichier

Enfin, on affiche un message informant l'utilisateur que le fichier est sauvegardé.


Maintenant que l'on a un chemin (cf)
et un nom de fichier (nf), on peut self.MessageBox("info","La liste de lecture est sauvegardee !")
ouvrir le fichier, imprimer notre en- On doit maintenant appeler cette routine depuis notre routine de gestion d'événement
tête M3U et parcourir la liste de lec- on_btnSauvegarderListe_clicked.
ture. Le chemin est stocké (si vous
vous souvenez) dans la colonne 2, le def on_btnSauvegarderListe_clicked(self,widget):
self.SauveListeLecture()
nom du fichier dans la colonne 0 et
l'extension dans la colonne 1. On Sauvegardez votre code et testez-le. Votre liste de lecture devrait être sauvegardée correctement et ressembler à
crée simplement (à droite) une l'exemple que je vous ai montré le mois dernier.
chaîne, puis on l'écrit dans le fichier ef on_boBtnHaut_clicked(self,widget):
et enfin on ferme le fichier. récupère la sélection puis la ligne sel = self.treeview.get_selection()
sélectionnée. Ensuite on doit par- (modele,lignes) = sel.get_selected_rows()
On peut maintenant commencer à courir les lignes pour récupérer 2 for chemin1 in lignes:
variables. Nous les appellerons che- chemin2 = 0
travailler sur les fonctions de dé- iter1=modele.get_iter(chemin1)
placement. Commençons par la rou- min1 et chemin2. chemin2 sera réglé iter2 = modele.get_iter(chemin2)
tine Haut. Comme nous l'avons fait à 0 dans ce cas, car c'est la ligne de modele.move_before(iter1,iter2)
en écrivant la fonction Supprimer, on «_destination ». chemin1 est la ligne

programmer en python volume 4 13


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
que l'utilisateur a sélectionnée. On dele.swap() (au milieu à droite). def on_boBtnBas_clicked(self,widget):
utilise enfin la méthode modele.mo- sel = self.treeview.get_selection()
ve_before() pour déplacer la ligne C'est la même chose pour la fonc- (modele,lignes) = sel.get_selected_rows()
sélectionnée sur la ligne 0, en pous- for chemin1 in lignes:
tion Descendre. Cette fois-ci, on vé-
chemin2 = self.NombreDeLignes­1
sant d'office tout vers le bas. Nous rifie que chemin2 est plus PETIT ou iter1=modele.get_iter(chemin1)
placerons le code (ci-contre à droite) égal à self.NombreDeLignes-1 (en iter2 = modele.get_iter(chemin2)
directement dans la routine on_boBtn bas à droite). modele.move_after(iter1,iter2)
Haut_clicked. def on_boBtnMonter_clicked(self,widget):
Maintenant, modifions quelques sel = self.treeview.get_selection()
Pour la fonction Bas, nous utilise- fonctionnalités de notre liste de lec- (modele,lignes) = sel.get_selected_rows()
rons presque le même code que pour for chemin1 in lignes:
ture. Dans l'article du mois dernier, je
chemin2 = (chemin1[0]­1,)
la routine Haut, mais au lieu d'utiliser vous ai montré le format de base if chemin2[0] >= 0:
la méthode modele.moveBefore(), d'une liste de lecture (en bas). iter1=modele.get_iter(chemin1)
nous utiliserons la méthode iter2 = modele.get_iter(chemin2)
modele.moveAf-ter() et, au lieu de modele.swap(iter1,iter2)
Cependant, je vous ai indiqué qu'il y
régler chemin2 à 0, on le réglera à avait aussi un format étendu. Dans le
def on_boBtnDescendre_clicked(self,widget):
self.NombreDeLignes-1. Maintenant format étendu, il y a une ligne sel = self.treeview.get_selection()
vous comprenez à quoi sert la supplémentaire que l'on peut ajouter (modele,lignes) = sel.get_selected_rows()
variable NombreDeLignes. Sou- au fichier avant chaque chanson, for chemin1 in lignes:
venez-vous que les lignes sont numé- contenant des informations supplé- chemin2 = (chemin1[0]+1,)
iter1=modele.get_iter(chemin1)
rotées à partir de 0, donc il faut mentaires sur la chanson. Le format if chemin2[0] <= self.NombreDeLignes­1:
utiliser NombreDeLignes-1 (en haut de cette ligne est le suivant : iter2 = modele.get_iter(chemin2)
à droite). modele.swap(iter1,iter2)
#EXTINF:[longueur de la
Maintenant regardons ce que donne chanson en secondes],[Nom de mutagen depuis le début alors qu'on informations des balises ID3 des fi-
la fonction Monter. À nouveau, elle
l'artiste] – [Titre de la ne l'a jamais utilisée. Eh bien, nous chiers MP3. Pour lire la discussion
chanson]
est très ressemblante aux deux allons l'utiliser maintenant. Pour vous complète là-dessus, reportez-vous au
fonctions que nous venons de créer. rafraîchir la mémoire, la bibliothèque numéro 35 du Full Circle qui contient
Vous vous demandiez peut-être
Cette fois-ci, on a chemin1 qui con- mutagen permet d'avoir accès aux la partie 9 de cette série. Nous crée-
pourquoi on a inclus la bibliothèque
tient la ligne sélectionnée, et on
règle chemin2 à NumeroLigne-1. #EXTM3U
Adult Contemporary/Chris Rea/Collection/02 ­ On The Beach.mp3
Ensuite, SI chemin2 (la ligne de Adult Contemporary/Chris Rea/Collection/07 ­ Fool (If You Think It's Over).mp3
destination) est supérieur ou égal à Adult Contemporary/Chris Rea/Collection/11 ­ Looking For The Summer.mp3
0, on utilise la méthode mo-

programmer en python volume 4 14


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
rons une fonction pour gérer la lec- Tant que nous y sommes, véri-fions def RecupererInfoMP3(self,nomFichier):
ture d'un fichier MP3 et renvoyer le si le nom de fichier existe et, si artiste = ''
nom de l'artiste, le titre de la chan- c'est le cas, prévenons l'uti- titre = ''
longueurChanson = 0
son et sa longueur en secondes, qui lisateur et sortons de la routine. audio = MP3(nomFichier)
sont les trois informations dont nous Aussi, pour rendre les choses un cles = audio.keys()
avons besoin pour la ligne des infor- peu plus faciles pour l'utilisateur for cle in cles:
mations étendues. Placez cette fonction et, puisqu'on ne supporte aucun try:
if cle == "TPE1": # Artiste
après la fonction APropos dans la autre type de fichier, ajoutons auto- artiste = audio.get(cle)
classe CreateurListeDeLecture (page matiquement l'extension .m3u au except:
suivante, en haut à droite). chemin et au nom de fichier si artiste = ''
elle n'y est pas déjà. Commençons try:
if cle == "TIT2": # Titre de la chanson
À nouveau, pour vous rafraîchir la par ajouter une ligne «_import titre = audio.get(cle)
mémoire, je vais parcourir le code. os.path » au début du code entre except:
Tout d'abord nous effaçons les trois les import de sys et de mutagen titre = ''
variables de retour pour qu'elles soient (à droite). longueurChanson = audio.info.length # longueur de la
chanson
renvoyées vides si quelque chose se return (artiste,titre,longueurChanson)
passe de travers. Ensuite on passe le
nom du fichier MP3 que nous allons
import os.path
examiner. Puis on place les clés dans
(vous l'avez deviné) un itérateur et Ensuite continuez et commentez la fonction SauveListeLecture actuelle et nous allons la remplacer.
on parcourt cet itérateur en cher-
chant les deux balises spécifiques. Ce def SavePlaylist(self):
sont TPE1 pour le nom de l'artiste et fp = self.txtPath.get_text() # Get the file path from the text box
fn = self.txtFilename.get_text() # Get the filename from the text box
TIT2 pour le titre de la chanson. Si if fp == "": # IF filepath is blank...
jamais la clé n'existe pas, on ob- self.MessageBox("error","Please provide a filepath for the playlist.")
tiendra une erreur, donc on entoure elif fn == "": # IF filename is blank...
chaque appel avec une instruction self.MessageBox("error","Please provide a filename for the playlist file.")
else: # Otherwise
try|except. Ensuite on va chercher la
longueur de la chanson dans l'attribut Jusqu'ici la routine est la même. Voici où les changements commencent.
audio.info.lengthetonretournetoutça.
debutExt = nf.rfind(".") # cherche le debut de l'extension
On va maintenant modifier la fonc- if debutExt == ­1:
nf += '.m3u' # ajouter une extension s'il n'y en a pas
tion SauvegarderListe pour qu'elle sup- self.txtNomFichier.set_text(nf) # remplace le nom de fichier dans le champ de texte
porte la ligne d'informations éten-dues.

programmer en python volume 4 15


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
Tout comme pour la fonction
else:
AjouterFichiers, nous utiliserons la fic = open(cf + "/" + nf,"w") # ouvre le fichier
méthode rfind pour trouver la fic.writelines('#EXTM3U\n') # affiche l'en­tete M3U
position du dernier point (« . ») dans for ligne in self.listeLecture:
nomFic = "%s/%s.%s" % (ligne[2],ligne[0],ligne[1])
le nom du fichier nf. S'il n'y en a pas,
artiste,titre,longueurChanson = self.RecupererInfoMP3(nomFic)
la valeur renvoyée sera -1. Donc nous if longueurChanson > 0 and (artiste != '' and titre != ''):
vérifions si la valeur retournée est -1 fic.writelines("#EXTINF:%d,%s ­ %s\n" %
et si c'est le cas on ajoute l'extension (longueurChanson,artiste,titre))
fic.writelines("%s\n" % nomFic)
et on replace le nom du fichier dans
fic.close # referme le fichier
le champ de texte pour être sympa. self.MessageBox("info","Liste de lecture sauvegardee !")

if os.path.exists(fp + "/" +
fn): fichier existe déjà, on puisse sim- La ligne 2 ouvre le fichier dans lequel des valeurs dans toutes ces variables.
plement sortir de la routine. On uti- nous allons écrire. La ligne 3 y place Si c'est le cas, on écrit la ligne d'in-
lise os.path.exists(nom du fichier) l'en-tête M3U. La ligne 4 règle un formations étendues à la ligne 8,
self.MessageBox(""erreur","L
pour cette vérification. parcours à travers la liste de lecture sinon on n'essaie pas. La ligne 9 écrit
e fichier existe deja.
Choisissez un autre nom.") ListStore. La ligne 5 crée le nom du la ligne du nom du fichier comme
Le reste du code sert principalement fichier à partir des trois colonnes de précédemment. La ligne 10 ferme gen-
Ensuite on veut entourer le reste de à sauvegarder comme précédem- ListStore. La ligne 6 appelle Recu- timent le fichier et la ligne 11 affiche
la fonction dans une clause IF|ELSE ment, mais regardons-le quand même. pererInfoMP3 et stocke les valeurs un message à l'utilisateur indiquant
(en haut à droite) pour que, si le renvoyées dans des variables. La que tout est terminé.
ligne 7 vérifie ensuite si nous avons
def SetupBullesAide(self):
self.boBtnAjouter.set_tooltip_text("Ajoute un ou des fichier(s) a la liste de lecture.")
self.boBtnAPropos.set_tooltip_text("Affiche les informations sur le programme.")
self.boBtnSupprimer.set_tooltip_text("Supprime l'entree selectionnee de la liste.")
self.boBtnEffacer.set_tooltip_text("Supprime toutes les entrees de la liste.")
self.boBtnQuitter.set_tooltip_text("Quitte le programme.")
self.boBtnHaut.set_tooltip_text("Deplace l'entree selectionne tout en haut de la liste.")
self.boBtnMonter.set_tooltip_text("Remonte l'entree selectionnee dans la liste.")
self.boBtnDescendre.set_tooltip_text("Descend l'entree selectionne dans la liste.")
self.boBtnBas.set_tooltip_text("Deplace l'entree selectionnee tout en bas de la liste.")
self.btnNomRepertoire.set_tooltip_text("Choisis le repertoire de sauvegarde de la liste.")
self.btnSauvegarderListe.set_tooltip_text("Sauvegarde la liste.")
self.txtNomFichier.set_tooltip_text("Entrez ici le nom du fichier a sauvegarder. L'extension .m3u sera ajoutee
pour vous si vous l'oubliez.")

programmer en python volume 4 16


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 23
et jolie, qui fait un travail merveilleux

EXTRA! EXTRA!
Allez, sauvegardez votre code et
essayez-le. de création de liste de lecture pour
vos fichiers de musique.
À ce stade, la seule chose qu'on

LISEZCECI
pourrait encore ajouter serait des Le code complet, incluant le fi-chier
bulles d'aide lorsque l'utilisateur sur- glade que nous avons créé le mois
vole nos contrôles avec sa souris. dernier, est disponible ici :
Cela y ajoute un air professionnel (ci- http://pastebin.com/ZfZ69zVJ
Des éditions spéciales
dessous). Créons maintenant une
fonction pour faire cela. Profitez des nouveaux talents que du magazine Full Circle
vous vous êtes découverts, jusqu'à la
prochaine fois.
sont sorties dans un
Nous utilisons le widget references
que nous avons réglé plus haut, puis
monde sans méfiance*
on règle le texte pour la bulle d'aide
avec (vous l'aurez deviné) l'attribut
set_tooltip_text. Ensuite on doit
ajou-ter l'appel à la routine.
Retournez dans la routine __init__,
après la ligne self.ReferencesWidgets,
ajoutez : LE SERVEUR PARFAIT
ÉDITION SPECIALE
self.SetupBullesAide()
Il s'agit d'une édition spéciale
Enfin et surtout (!), on veut placer du Full Circle qui est une ré- PYTHON
notre logo dans la boîte APropos. édition directe des articles Le ÉDITION SPECIALE n° 1
Comme tout le reste ici, il y a un Serveur parfait qui ont déjà été
attribut pour faire cela. Ajoutez la publiés dans le FCM n° 31 à 34. Il s'agit d'une reprise de Pro-
ligne suivante à la routine APropos : grammer en Python, parties 1 à
http://fullcirclemagazine.org/ 8 par Greg Walters.
apropos.set_logo(gtk.gdk.pix special-edition-1-the-perfect-
buf_new_from_file(“logo.png”
server/ http://fullcirclemagazine.org/
)) python-special-edition-1/

Et voilà. Vous avez maintenant une * Ni Full Circle magazine, ni ses concepteurs ne s'excusent pour l'hystérie
éventuellement causée par la sortie de ces publications.
application complète, fonctionnelle

programmer en python volume 4 17


TUTORIEL Programmer en Python - Partie 24
Greg Walters

W
aouh ! Il est difficile application simple qui permet d'im- porter « os ». Puis à la ligne 2, nous
de croire que ceci est primer directement sur votre impri- avons ouvert « lpr » avec un accès en
déjà le 24e numéro. mante : écriture, en l'assignant à la variable Waouh ! Il est difficile


Cela fait deux ans objet « pr ». Nous procédons alors à de croire que ceci est
que nous apprenons le Python ! Vous import os une écriture « pr.write » avec tout ce déjà le 24e numéro.
que nous voulons imprimer. Enfin Cela fait deux ans que
avez parcouru un très long chemin. pr = os.popen('lpr','w')
(ligne 5), nous fermons le fichier ce nous apprenons le
qui va envoyer les données vers Python !
Cette fois-ci, nous allons traiter deux pr.write('Test imprimante
sujets. Le premier est l'impression depuis linux via python\n') l'imprimante.
PyRTF
sur une imprimante, le second est la pr.write('Impression
création de fichiers RTF (Rich Text Nous pouvons également créer un
terminee\n') Maintenant occupons-nous des fi-
Format, ou Format de Texte Riche) fichier texte puis l'envoyer à l'impri-
chiers RTF. Le format RTF (c'est com-
comme sortie. pr.close() mante comme ceci…
me quand on dit le numéro PIN puis-
C'est assez facile à comprendre si import os que PIN signifie Numéro d'Identifi-
Impression générique vous élargissez un peu votre esprit. cation Personnel et que ça revient à
sous Linux Dans le code ci-dessus, « lpr » est le filename = 'fichier.bidon' dire le Numéro Numéro d'Identifica-
spooler d'impression. Le seul prérequis tion Personnel [Ndt : en français on
os.system('lpr %s' %
Commençons donc avec l'impression est que nous ayons déjà configuré « filename) n'a pas ce problème de redondance
sur une imprimante. L'idée de parler lpd » et qu'il fonctionne. C'est très puisqu'on parle de code PIN] : ça dé-
de cela provient d'un courriel envoyé probablement déjà fait pour vous si Dans ce cas, nous utilisons toujours pend du département Département
par Gord Campbell. Il est réellement vous utilisez une imprimante sous l'objet lpr mais avec la commande des Redondances, non ?) a été créé à
facile de faire la plupart des impres- Ubuntu. « lpd » est généralement con- «  os.system » qui sert simplement à l'origine par Microsoft en 1987 et sa
sions depuis Linux ; plus facile qu'avec sidéré comme un « filtre magique » envoyer à Linux une commande comme syntaxe s'est inspirée du langage de
cet autre système d'exploitation qui qui permet de convertir automati- si on l'avait saisie depuis un terminal. composition de texte TeX. PyRTF est
commence par « WIN » - et dont je ne quement différents types de documents une merveilleuse bibliothèque qui
parlerai pas. en quelque chose que l'imprimante peut Je vous laisserai vous amuser un peu facilite la création de fichiers RTF.
comprendre. Nous allons imprimer sur avec cela. Cela nécessite de réfléchir en amont
Tout est plutôt facile tant que vous le périphérique/objet « lpr ». Pensez- à ce à quoi le fichier doit ressembler,
ne souhaitez imprimer que du texte y comme à un simple fichier. Nous mais le résultat en vaut vraiment la
simple, sans gras, italique, chan- ouvrons le fichier ; nous devons im- peine.
gements de polices, etc. Voici une
programmer en python volume 4 18
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 24

Tout d'abord il faut télécharger et Nous avons déjà parlé de la routine Regardons ce que nous avons fait. La LibreOffice) pour ouvrir le fichier et
installer le paquet pyRTF. Allez sur __name__ précédemment, mais pour première ligne crée une instance de l'examiner.
http://pyrtf.sourceforge. et récupé- vous rafraîchir la mémoire je vous document. Puis on crée une instance
rez le paquet PyRTF-0.45.tar.gz. Sauve- rappelle que si nous exécutons le de feuille de style. Ensuite nous créons Maintenant faisons quelques modi-
gardez-le quelque part et utilisez le programme en mode autonome la une instance de l'objet section et on fications sympathiques. Tout d'abord,
gestionnaire d'archives pour le décom- variable interne __name__ est réglée l'ajoute au document. Imaginez une ajoutons un en-tête. Là encore l'auteur
presser. Puis ouvrez un terminal et à « __main__ » ; par contre, si on section comme un chapitre dans un de pyRTF nous fournit un style prédéfini
déplacez-vous à l'endroit où vous l'avez l'appelle comme « import » depuis un livre. Ensuite nous créons un paragraphe appelé Header1, que nous allons uti-
décompressé. Tout d'abord il faut ins- autre programme, cette portion de en utilisant le style Normal. L'auteur liser pour notre en-tête. Ajoutez ce qui
taller le paquet, avec la commande code sera ignorée. de pyRTF a préréglé ce style avec suit entre les lignes docu.Sections.append
« sudo python setup.py install ». Remar- une police Arial en 11 points. Ensuite et p = Paragraph.
quez qu'il y a un répertoire d'exemples, Nous créons là une instance de on écrit le texte qu'on veut dans ce para-
p =
qui contient de bonnes informations l'objet Renderer, appelons la routine graphe, on l'ajoute à la section et on
Paragraph(ss.ParagraphStyles.
pourfairedeschosesunpeucompliquées. FabriqueExemple et récupérons l'objet retournenotredocumentdocu. Heading1)
retourné docu. Puis nous écrivons le
Nous y voilà. Commençons comme fichier (docu) en utilisant la routine C'est vraiment facile. Encore une fois, p.append('Exemple d'en­tete')

d'habitude en créant le canevas de OuvreFichier. vous devez réfléchir soigneusement


section.append(p)
notre programme que vous pouvez en amont à la sortie désirée, mais ce
voir en haut à droite. Avant d'aller Passons maintenant au contenu de la n'est pas très compliqué. Modifiez le nom du fichier en
plus loin, parlons de ce qui se passe. routine principale FabriqueExemple. Sauvegardez ce programme en tant «_rtftestb » ; cela devrait donner ceci :
La ligne 2 importe la bibliothèque Remplacez l'instruction pass par le que « rtftesta.py » et exécutez-le.
pyRTF. Remarquez que nous utilisons code ci-dessous. Enfin, utilisez OpenOffice (ou DR.Write(docu,
OuvreFichier('rtftestb'))
un format d'importation différent des
autres fois : cette fois-ci nous impor-
docu = Document()
tons tout ce qui se trouve dans la ss = doc.StyleSheet
biblothèque. Notre routine principale section = Section()
s'appelle FabriqueExemple et ne fait docu.Sections.append(section)
rien pour le moment. La routine Ou-
p = Paragraph(ss.ParagraphStyles.Normal)
vreFichier crée un fichier avec pour p.append('Voici notre premier exemple de création de fichier RTF. '
nom celui passé en argument, lui ajoute 'Ce premier paragraphe est dans le style prédéfini appelé normal '
l'extension .rtf, le place en mode 'et tous les paragraphes suivants utiliseront ce style sauf si on le change.')
écriture et retourne un pointeur sur section.append(p)
ce fichier. return docu

programmer en python volume 4 19


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 24
Sauvegardez-le sous le nom p = Paragraph(ss.ParagraphStyles.Normal)
rtftestb.py et exécutez-le. Maintenant p.append( 'Il est aussi possible de passer outre les elements d''un style. ',
nous avons un en-tête. Je suis sûr que 'Par exemple vous pouvez modifier seulement la ',
TEXT(' taille de la police a 24 points', size=48),
votre esprit est en train de se ' ou',
demander tout ce qu'on peut faire TEXT(' son type a Impact', font=ss.Fonts.Impact),
encore. Voici une liste des styles ' ou meme d''autres attributs comme',
prédéfinis que l'auteur nous fournit. TEXT(' LA GRAISSE',bold=True),
TEXT(' ou l''italique',italic=True),
TEXT(' ou LES DEUX',bold=True,italic=True),
Normal, Normal Short, Heading 1, '.' )
Heading 2, Normal Numbered, Nor- section.append(p)
mal Numbered 2. Il y a également un
Sauvegardez le fichier sous le nom à la suite la commande « size = ». Mais p = Paragraph()
rtftestc.py et exécutez-le. La nouvelle attendez une minute : on indique 48
Voyons maintenant
portion du document devrait ressem- comme taille alors qu'on veut écrire p.append('Voici un nouveau


comment modifier les paragraphe avec le mot ',
bler à ceci… en 24 points ; et la sortie est réel-
polices, leur taille et
lement en 24 points. Que se passe-t-il
leurs attributs (gras,
italique, etc.) à la Il est également possible de passer ici ? Eh bien, la commande de taille est TEXT('ROUGE',colour=ss.Colou
volée. outre les éléments d'un style. Par en demi-points ; ainsi si on veut écrire rs.Red),
exemple vous pouvez modifier seule- en police 8 points, on doit utiliser ' ecrit en rouge.')
style List que je vous laisserai décou- ment la taille de la police à 24 points, « size = 16 ». Vous comprenez ?
vrir. Si vous voulez en voir davantage, ou son type à Impact ou même mo- section.append(p)
sur ça et sur d'autres sujets, les styles difier d'autres attributs comme la Ensuite, on continue le texte et on
sont définis dans le fichier Elements.py graisse ou l'italique ou les deux. modifie la police avec la commande Remarquez que nous n'avons pas eu
que vous avez installé tout à l'heure. «_font = ». Cette fois encore, tout ce à repréciser que le style de para-
Bon, qu'avons-nous fait ? La ligne 1 crée qui est dans l'instruction en ligne graphe est Normal, puisqu'il ne
Ces styles prédéfinis sont utiles pour un nouveau paragraphe. On commence TEXT entre les guillemets sera affecté, change pas tant qu'on ne lui dit pas.
beaucoup de choses, mais on peut comme auparavant à l'ajouter au mais pas le reste. Remarquez également que si vous
avoir besoin d'en créer d'autres. Voyons texte. Regardez la ligne 4 (TEXT(' taille habitez aux États-Unis vous devez
maintenant comment modifier les de la police a 24 points', size=48),) : en Bien. Si vous avez compris tout cela, utiliser la bonne orthographe pour
polices, leur taille et leurs attributs utilisant le qualificatif TEXT on indique que peut-on faire d'autre ? «  Colours_» [Ndt : les Américains
(gras, italique, etc.) à la volée. Après à pyRTF qu'il faut faire quelque chose utilisent souvent l'orthographe «  im-
notre paragraphe, et avant de retour- de différent au milieu de la phrase, On peut aussi régler la couleur du propre » Color].
ner l'objet document, insérez le code dans ce cas on modifie la taille de la texte avec l'instruction en ligne TEXT
ci-dessus à droite et modifiez le nom police (Arial) à 24 points, en précisant de cette façon : Voici les couleurs prédéfinies : Black,
du fichier de sortie en rtftestc.
programmer en python volume 4 20
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 24
Blue, Turquoise, Green, Pink, Red, Yellow, p = Paragraph(ss.ParagraphStyles.Courier)
White, BlueDark, Teal, GreenDark, p.append('Now we are using the Courier style at 8 points. '
Violet, RedDark, YellowDark, GreyDark 'All subsequent paragraphs will use this style automatically. '
'This saves typing and is the default behaviour for RTF documents.',LINE)
et Grey. section.append(p)
p = Paragraph()
Et voici une liste de toutes les polices p.append('Also notice that there is a blank line between the previous paragraph ',
prédéfinies (ce sont les notations 'and this one. That is because of the "LINE" inline command.')
pour les utiliser) : section.append(p)

Arial, ArialBlack, ArialNarrow, Bit- NormalText.Copy()) styles. Recopiez simplement le code pour afficher en style ArialGrasRouge.
streamVeraSans, BitstreamVeraSerif, du style et remplacez les informa-
BookAntiqua, BookmanOldStyle, Cas- result.ParagraphStyles.appen
d(ps2) tions de police et de taille comme vous Tableaux
tellar, CenturyGothic, ComicSansMS, le voulez. On peut aussi faire cela :
CourierNew, FranklinGothicMedium,
Avant d'écrire le code pour l'utiliser, Souvent, la seule manière de pré-
Garamond, Georgia, Haettenschweiler, NormalText =
regardons ce que nous avons fait. senter correctement des données
Impact, LucidaConsole, LucidaSansUni- TextStyle(TextPropertySet
Nous créons une nouvelle instance (result.Fonts.Arial,22,bold= dans un document est d'utiliser un
code, MicrosoftSansSerif, PalatinoLino-
de feuille de style nommée result. À True,colour=ss.Colours.Red)) tableau. Faire des tableaux dans un
type, MonotypeCorsiva, Papyrus, Syl-
la deuxième ligne nous réglons la texte est plutôt difficile, mais PAR-
faen, Symbol, Tahoma, TimesNew- ps2 =
police à CourierNew en 8 points puis FOIS c'est plutôt facile avec pyRTF.
Roman, TrebuchetMS et Verdana. ParagraphStyle('ArialGrasRouge'
« enregistrons » le style comme Courier. ,NormalText.Copy()) J'expliquerai cela plus tard dans cet
Souvenez-vous que nous devons in- article.
Maintenant vous devez penser que
diquer 16 comme taille puisque ce result.ParagraphStyles.append
tout cela est bien joli, mais comment (ps2)
sont des demi-points. Regardons un tableau standard (ci-
peut-on créer ses propres styles ?
C'est assez simple. Retournez en haut dessous) dans OpenOffice/LibreOf-
Et ajouter le code suivant :
Maintenant, ajoutons un nouveau pa- fice. Cela ressemble à une feuille de
de notre fichier et ajoutez le code qui
ragraphe en utilisant le style Cou- calcul, où tout est placé dans des
suit avant la ligne d'en-tête. p =
rier, avant la ligne return en bas de la Paragraph(ss.ParagraphStyles. colonnes.
result = doc.StyleSheet routine. ArialGrasRouge)

NormalText = Maintenant que vous avez un nou- p.append(LINE,'Et


TextStyle(TextPropertySet maintenant on
veau style, vous pouvez l'utiliser quand utilise le style
(result.Fonts.CourierNew,16)
) vous le souhaitez. Vous pouvez uti- ArialGrasRouge.',LINE)
liser n'importe quelle police de la
ps2 = liste ci-dessus et créer vos propres section.append(p)
ParagraphStyle('Courier',
programmer en python volume 4 21
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 24
Des lignes horizontales, et des co- Nous créons un tableau à trois co- #!/usr/bin/env python
lonnes verticales. Un concept simple. lonnes, la première contient 7 cellules,
les deux suivantes en contiennent 3. from PyRTF import *
Commençons une nouvelle appli- Nous n'avons pas que des cellules def ExempleTableau():
cation nommée rtfTableau-a.py. Démar- uniques à notre disposition, car on pass
rons avec notre code standard (page pourra saisir les largeurs en « twips »
suivante) et construisons à partir de là. [Ndt : ce sont des unités de mesure, def OuvreFichier(nom):
return file('%s.rtf' % nom, 'w')
utilisées en LibreOffice et pour le RTF,
Pas besoin d'explications ici puisque équivalentes à 1/1440 d'un pouce if __name__ == '__main__':
c'est à peu près le même code que nous (2,54 cm). Cf http://en.wikipedia.org/wi- DR = Renderer()
avons utilisé précédemment. Maintenant, ki/Twip#In_computing.] Nous y revien- docu = ExempleTableau()
DR.Write(docu, OuvreFichier('rtftable­a'))
écrivons la routine ExempleTableau. drons dans un moment. print "Fini"
J'utilise en partie l'exemple fourni par c1 = Cell(Paragraph('ligne 1,
l'auteur de pyRTF. Remplacez l'instruc- cellule 1')) Ce morceau de code règle les Ceci ajoute le tableau dans la section
tion pass dans la routine par le code données pour la deuxième ligne. etretourneledocumentpouraffichage.
c2 = Cell(Paragraph('ligne 1,
suivant : Remarquez que nous pouvons régler
cellule 2'))
des styles différents pour une seule Sauvegardez et exécutez l'applica-
docu = Document() c3 = Cell(Paragraph('ligne 1, ou plusieurs cellules. tion. Vous remarquerez que tout res-
cellule 3'))
ss = docu.StyleSheet
semble à ce que vous attendiez, mais
c1 =
tableau.AddRow(c1,c2,c3) Cell(Paragraph(ss.ParagraphS qu'il n'y a pas de bordures pour le
section = Section() tyles.Heading2,'Style en­ tableau. Ceci peut rendre les choses
Ici nous réglons les données qui vont tete 2')) difficiles à lire : réglons ce problème.
docu.Sections.append(section)
dans chaque cellule de la première c2 = À nouveau, j'utilise en grande partie
ligne. Cell(Paragraph(ss.ParagraphS le code de l'exemple fourni par l'au-
Cette partie est la même que précé- tyles.Normal,'Retour au teur de pyRTF.
demment, passons à la suite. c1 = style Normal'))
Cell(Paragraph(ss.ParagraphS
tableau =
tyles.Heading2,'Style en­ c3 = Cell(Paragraph('Encore Sauvegardez votre fichier sous le
Table(TabPS.DEFAULT_WIDTH * 7,
tete 2')) du style Normal')) nom rtfTableau-b.py, puis effacez
c2 = tableau.AddRow(c1,c2,c3) tout ce qui est entre « docu.Sec-
TabPS.DEFAULT_WIDTH * 3, Cell(Paragraph(ss.ParagraphS tions.append(section) » et « return
TabPS.DEFAULT_WIDTH * 3)
tyles.Normal,'Retour au
Ceci règle la dernière ligne. docu » dans la routine ExempleTa-
style Normal'))
bleau, et remplacez-le par ce qui suit :
c3 = Cell(Paragraph('Encore section.append(tableau)
Cette ligne (oui, il ne s'agit que d'une du style Normal')) cote_fin = BorderPS( width=20,
ligne, mais découpée pour plus de return docu
style=BorderPS.SINGLE )
clarté) crée notre tableau basique. tableau.AddRow(c1,c2,c3) cote_epais = BorderPS( width=80,
programmer en python volume 4 22
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 24
style=BorderPS.SINGLE ) Aucune des cases n'aura de bordure
dans la ligne 2.
bord_fin = FramePS( cote_fin,
cote_fin, cote_fin, cote_fin )
bord_epais = FramePS( cote_epais, c1 = Cell( Paragraph( 'L3C1' ),
cote_epais, cote_epais, cote_epais ) bord_mixte )

bord_mixte = FramePS( cote_fin, c2 = Cell( Paragraph( 'L3C2' ) )


cote_epais, cote_fin, cote_epais )
c3 = Cell( Paragraph( 'L3C3' ),
bord_mixte )
Ici nous réglons les définitions des côtés
et des bords pour les encadrements. tableau.AddRow( c1, c2, c3 )

tableau = Table( À nouveau, les cases des colonnes 1


TabPS.DEFAULT_WIDTH * 3, et 3 auront une bordure mixte dans
TabPS.DEFAULT_WIDTH * 3,
TabPS.DEFAULT_WIDTH * 3 ) la troisième ligne.

c1 = Cell( Paragraph( 'L1C1' ), section.append( tableau )


bord_fin )
Et voilà. Vous avez maintenant les
c2 = Cell( Paragraph( 'L1C2' ) )
bases pour créer des documents RTF
c3 = Cell( Paragraph( 'L1C3' ), avec du code.
bord_epais )
À la prochaine fois !
tableau.AddRow( c1, c2, c3 )

Le code source est disponible sur


Dans la première ligne, les cellules de
pastebin comme d'habitude. La pre-
la colonne 1 (bord_fin) et de la colonne
mière partie est ici : http://paste-
3 (bord_epais) auront une bordure.
bin.com/uRVrGjkV et contient le
c1 = Cell( Paragraph( 'L2C1' ) ) résumé de rtftest.py (a à e), la seconde
partie rtfTableau.py (a et b) est ici :
c2 = Cell( Paragraph( 'L2C2' ) ) http://pastebin.com/L8DGU7Lz.
c3 = Cell( Paragraph( 'L2C3' ) )

tableau.AddRow( c1, c2, c3 )

programmer en python volume 4 23


TUTORIEL Programmer en Python - Partie 25
Greg Walters

U
n certain nombre d'entre liser les fonc-tions de l'interface gra- peut être un autre
vous ont commenté les ar- phique de ce langage. Il y a un certain widget. Nous ver- COLUMNS ­ >
ROWS | 0,0 | 1,0 | 2,0 | 3,0 | 4,0 |
ticles de programmation nombre de widgets qui viennent nati- rons cela le mois | | 0,1 | 1,1 | 2,1 | 3,1 | 4,1 |
graphique et dit combien vement avec le module Tkinter. Parmi prochain. Pour ce | 0,2 | 1,2 | 2,2 | 3,2 | 4,2 |
vous les avez appréciés. En réponse à eux, on trouve des conteneurs de haut mois-ci, tous les | 0,3 | 1,3 | 2,3 | 3,3 | 4,3 |
cela, nous allons commencer à jeter niveau (des fenêtres principales), des widgets auront pour
un œil à un autre outil d'interfaces boutons, des étiquettes, des cadres, parent la fenêtre
graphiques appelé Tkinter. Ceci est la des zones de saisie de texte, des racine. simple de 5 colonnes sur 4 lignes (en
façon «  officielle  » de faire de la cases à cocher, des boutons radio, Afin de placer et d'afficher les wid- haut à droite). Le parent possède la
programmation graphique en Python. des canevas, des entrées de texte gets enfants, nous devons utiliser ce grille, les widgets vont dans les posi-
Tkinter existe depuis longtemps et a multilignes, et bien plus encore. Il y a qu'on appelle la « gestion de géomé- tions de la grille. Au premier regard,
une assez mauvaise réputation pour aussi de nombreux modules qui ajoutent trie  ». C'est la façon dont les choses vous pourriez penser que cela est
son côté «  démodé  ». Ceci a changé des fonctionnalités par dessus Tkin- se placent dans la fenêtre racine prin- très limitatif. Toutefois, les widgets
récemment, alors j'ai pensé que nous ter. Ce mois-ci, nous allons nous con- cipale. La plupart des programmeurs peuvent s'étendre sur plusieurs posi-
pourrions nous battre contre ce mau- centrer sur quatre widgets. Un conteneur utilisent un de ces trois types de tions sur la grille, soit dans le sens
vais processus de réflexion. de haut niveau (à partir d'ici je vais gestion de géométrie  : Packer, Grid, des colonnes, soit dans celui des lignes,
N.B. : Tout le code présenté ici est pour essentiellement l'appeler la fenêtre ou Gestion de la place. À mon humble ou les deux à la fois.
Python 2.x seulement. Dans un pro- racine), un cadre, des étiquettes et des avis, la méthode Packer est très mala-
chain article, nous allons discuter de boutons. Dans le prochain article, nous droite. Je vous laisse l'explorer par Notre premier exemple
la façon d'utiliser Tkinter avec Python verrons plus de widgets plus en pro- vous-même. La méthode de gestion
3.x. Si vous DEVEZ utiliser Python 3.x, fondeur. de la place permet un placement Notre premier exemple est SUPER
changez les déclarations d'importation extrêmement précis des widgets, mais simple (seulement quatre lignes), mais
en « from tkinter import * ». Fondamentalement, nous avons le ça peut être compliqué. Nous en repar- explicite.
widget conteneur de haut niveau qui lerons dans un futur article. Cette
contient d'autres widgets. Il s'agit de fois-ci, nous allons nous concentrer
Un peu d'histoire et un la fenêtre racine ou principale. Dans sur la méthode de la grille.
from Tkinter import_*

peu de contexte cette fenêtre racine, nous plaçons les


racine = Tk()

widgets que nous voulons utiliser dans Pensez à un tableur. Il y a des lignes bouton = Bouton(racine, text
Tkinter est l'abbréviation de «  Tk notre programme. Chaque widget, à et des colonnes. Les colonnes sont = "Bonjour FullCircle").grid()
interface ». Tk est un langage de pro- l'exception du conteneur racine verticales, les lignes sont horizontales. racine.mainloop()
grammation à lui tout seul, et le principal, a un parent. Le parent n'est Voici une représentation texte simple
module Tkinter nous permet d'uti- pas forcément la fenêtre racine  ; ça des adresses de cellule d'une grille
programmer en python volume 4 24
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
Bon, qu'est-ce qui se passe ici ? La pre- class App:
mière ligne importe la bibliothèque def __init__(self, principale):
Tkinter. Ensuite, on instancie l'objet cadre = Frame(principale)
self.lblTexte = Label(cadre, text = "Voici un widget label")
Tk racine (Tk est une partie de Tkinter). self.btnQuitter = Button(cadre, text="Quitter", fg="red", command=cadre.quit)
Voici la ligne trois : self.btnBonjour = Button(cadre, text="Bonjour", command=self.DitUnTruc)
cadre.grid(column = 0, row = 0)
bouton = Button(racine, text self.lblTexte.grid(column = 0, row = 0, columnspan = 2)
= "Bonjour FullCircle").grid() self.btnBonjour.grid(column = 0, row = 1)

Nous créons un bouton appelé bou-


ton, définissons son parent à la
Commençons : l'appelons self.lblTexte. Il hérite de l'ob- voulons utiliser lorsque l'utilisateur
fenêtre racine, réglons son texte à
jet widget Label. Nous réglons son clique sur le bouton. Dans le cas de
«  Bonjour FullCircle  » et le plaçons from Tkinter import * parent (le cadre) et définissons le btnQuitter, c'est cadre.quit, qui termine
dans la grille. Enfin, nous appelons la
texte à afficher (text = “Ceci est un le programme. C'est une fonction
boucle principale de la fenêtre. Ça C'est la déclaration d'importation pour widget label”). C'est aussi simple que intégrée, donc nous n'avons pas besoin
paraît très simple quand on regarde la bibliothèque Tkinter. cela. Bien sûr, nous pouvons faire de la créer. Dans le cas de btnBon-
le code, mais beaucoup de choses se
beaucoup mieux, mais pour l'instant jour, c'est une routine appelée
passent dans les coulisses. Heureu- Nous définissons notre classe, et dans c'est tout ce dont nous avons besoin. self.DitUnTruc. Nous devons créer
sement, nous n'avons pas besoin de la routine __init__, nous mettons en Ensuite, nous mettons en place les celle-ci, mais auparavant nous avons
comprendre tout cela pour l'instant. place nos widgets et les plaçons dans deux boutons que nous allons utiliser : encore quelque chose à faire.
la grille.
Exécutez le programme et nous self.btnQuitter = Nous devons placer nos widgets dans
allons voir ce qui se passe. Sur ma La première ligne dans la routine Button(cadre, text="Quitter",
la grille. Voici les lignes à nouveau :
machine, la fenêtre principale __init__ crée un cadre qui sera le
fg="red", command=cadre.quit)
self.btnBonjour =
apparaît en bas à gauche de l'écran. parent de tous nos autres widgets.
cadre.grid(column = 0, row =
Button(cadre, text="Bonjour", 0)
Elle pourrait appa-raître ailleurs sur le Le parent de ce cadre est la fenêtre command=self.DitUnTruc)
vôtre. Cliquer sur le bouton ne fait racine (widget de plus haut niveau). self.lblTexte.grid(column =
rien. Réparons cela dans notre Ensuite, nous définissons un label et Nous nommons les widgets, fixons
0, row = 0, columnspan = 2)
prochain exemple. deux boutons. Regardons la ligne de leur parent (cadre) et définissons le self.btnBonjour.grid(column
création de l'étiquette. texte à afficher. Maintenant btnQuitter = 0, row = 1)
Notre deuxième exemple a un attribut marqué fg que nous self.btnQuitter.grid(column
self.lblTexte = Label(cadre, avons réglé à « red ». Vous avez = 1, row = 1)
text = "Ceci est un widget
Cette fois, nous allons créer une deviné que cela définit la couleur
label")
classe appelée App. Ce sera la classe d'avant-plan ou la couleur du texte à Tout d'abord, nous attribuons une
qui détient effectivement notre fenêtre. la couleur rouge. Le dernier attribut grille au cadre. Ensuite, nous réglons
Nous créons le widget étiquette et
sert à définir la commande que nous l'attribut de grille de chaque widget
programmer en python volume 4 25
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
selon l'endroit où nous voulons placer racine = Tk() zontale de 550 pixels (depuis la ­­­­­­­­­­­­­­­­­
le widget. Notez la ligne « columnspan » app = App(racine) droite) et à une position verticale de | 0 |
pour l'étiquette (self.lblTexte). Cela 150 pixels (depuis le haut). Comment ­­­­­­­­­­­­­­­­­
racine.mainloop() | 1 | 2 | 3 | + |
indique que nous voulons que l'éti- suis-je arrivé à ces chiffres ? J'ai com- ­­­­­­­­­­­­­­­­­
quette s'étende sur deux colonnes de mencé avec des valeurs raisonnables | 4 | 5 | 6 | ­ |
Essayez le programme. Maintenant,
la grille. Puisque nous avons seulement et les ai peaufiné à partir de là. C'est ­­­­­­­­­­­­­­­­­
il fait vraiment quelque chose. Mais | 7 | 8 | 9 | * |
deux colonnes, il s'agit de toute la un peu difficile de faire de cette
là encore, la position de la fenêtre ­­­­­­­­­­­­­­­­­
largeur de l'application. Maintenant façon, mais les résultats sont meilleurs
est très gênante. Corrigeons cela | ­ | 0 | . | / |
nous pouvons créer notre fonction que si on ne fait rien du tout. ­­­­­­­­­­­­­­­­­
dans notre prochain exemple.
de rappel : | = |
­­­­­­­­­­­­­­­­­
def DitUnTruc(self): Notre troisième exemple Notrequatrièmeexemple- | CLEAR |
Unecalculatricesimple ­­­­­­­­­­­­­­­­­
print "Bonjour lecteur
du Magazine FullCircle !!"
Enregistrez l'exemple précédent
sous le nom exemple3.py. Tout est Maintenant, regardons quel-
exac-tement pareil, sauf une seule que chose d'un peu plus from Tkinter import *
Cela affiche simplement dans la fe-
ligne qui se trouve en bas de la rou- compliqué. Cette fois, nous def StartUp():
nêtre du terminal le message “Bon-
tine principale. Je vais vous montrer allons créer une calcu- global val, w, root
jour lecteur du Magazine FullCir-
ces lignes avec la nouvelle : latrice simple à 4 bou- root = Tk()
cle !!” Enfin, on instancie la classe Tk root.title('Easy Calc')
tons, pour les 4 opéra-
- notre classe App - et exécutons la root.geometry('247x330+469+199')
root = Tk() tions:addition,soustraction,
boucle principale : w = Calculator(root)
root.geometry('150x75+550+150') multiplication et division. root.mainloop()
À droite vous voyez à
app = App(root) quoi elle ressemblera, sous forme de continuez.
class Calculator(): root.mainloop() texte simple.
def __init__(self,root): Nous commençons notre définition
master = Frame(root) Ceci force notre fe- Nous allons y plonger tout de suite de la classe en mettant en place
et je vous expliquerai le code (au notre fonction __init__. Nous réglons
self.CurrentValue = 0
self.HolderValue = 0 nêtre initiale à une
self.CurrentFunction = '' taille de 150 pixels milieu à droite) au fur et à mesure. trois variables comme suit :
self.CurrentDisplay = StringVar() de large sur 75 pixels
self.CurrentDisplay.set('0') de haut. Nous vou- À part la déclaration de la géométrie, • ValeurCourante - Contient la valeur
self.DecimalNext = False
lons aussi que le coin ceci devrait être assez facile pour actuelle qui a été entrée dans la cal-
self.DecimalCount = 0
self.DefineWidgets(master) supérieur gauche de vous de comprendre maintenant (à culatrice.
self.PlaceWidgets(master) la fenêtre soit placé gauche). Rappelez-vous, prenez des
à une position hori- valeurs raisonnables, modifiez-les, puis • ValeurAncienne - Contient la valeur

programmer en python volume 4 26


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
qui existait avant que l'utilisateur Label(principale,
ne clique sur une touche de anchor=E,relief = self.btn1 = Button(master, text = '1',width = 4,height=3)
SUNKEN,bg="white", self.btn1.bind('<ButtonRelease­1>', lambda e: self.funcNumButton(1))
fonction. height=2,textvar self.btn2 = Button(master, text = '2',width = 4,height=3)
iable=self.Affic self.btn2.bind('<ButtonRelease­1>', lambda e: self.funcNumButton(2))
• FonctionCourante - C'est tout sim- hageCourant) self.btn3 = Button(master, text = '3',width = 4,height=3)
plement pour se souvenir quelle self.btn3.bind('<ButtonRelease­1>', lambda e: self.funcNumButton(3))
self.btn4 = Button(master, text = '4',width = 4,height=3)
fonction est traitée. Bon, nous avons déjà self.btn4.bind('<ButtonRelease­1>', lambda e: self.funcNumButton(4))
défini un label aupara-
Ensuite, nous définissons la variable vant. Cependant, cette
AffichageCourant et l'attribuons à fois, nous ajoutons un plat), SUNKEN (en creux), RAISED exactement le même. Encore une
l'objet StringVar. C'est un objet certain nombre d'autres attributs. (en relief), GROOVE (en strie) et fois, nous avons créé des boutons
spécial qui fait partie de la trousse Notez que nous n'utilisons pas RIDGE (en arête). La valeur par défaut plus tôt dans ce tutoriel, mais nous
Tkinter. Quel que soit le widget l'attribut «  text  ». Ici, nous assi- est à plat si vous ne spécifiez rien. allons regarder de plus près ce que
auquel vous l'attribuez, cela met gnons l'étiquette au parent (la fe- N'hésitez pas à essayer les autres nous faisons ici.
automatiquement à jour la valeur nêtre principale), puis nous définis- combinaisons par vous-mêmes lorsque
dans le widget. Dans ce cas, nous sons l'ancre (ou, pour nos fins, la nous aurons fini. Ensuite, nous défi- Nous commençons par définir le
allons l'utiliser pour contenir ce que justification) pour le texte lorsqu'il nissons le fond (bg) à blanc afin de parent (la fenêtre principale), le texte
nous voulons que le widget d'affi- est écrit. Dans ce cas, nous précisons le démarquer un peu du reste de la que nous voulons sur le bouton, et
chage label… euh… eh bien… affiche. à l'étiquette de justifier tout le fenêtre. Nous fixons la hauteur à 2 la largeur et la hauteur. Notez que
Nous devons l'instancier avant de texte à l'est, c'est-à-dire sur le côté (qui signifie deux lignes de texte de la largeur est en caractères et la
pouvoir l'assigner au widget. Ensuite, droit du widget. Il existe un attribut haut, et non pas 2 pixels) et enfin hauteur est en lignes de texte. Si
nous utilisons la fonction « set » four- de justification, mais il sert lorsqu'il nous assignons la variable que nous vous vouliez un graphique dans le
nie par Tkinter. Nous définissons en- y a plusieurs lignes de texte. L'attri- venons de définir juste avant (self.Affi- bouton, vous utiliseriez des pixels
suite une variable boléenne appelée but d'ancrage a les options suivantes : chageCourant) à l'attribut textvariable. pour définir la hauteur et la largeur.
PartieDecimale et une variable Comp- N, NE, E, SE, S, SW, W, NW et À chaque fois que la valeur de self.Af- Cela peut devenir un peu confus
teDecimales, puis nous appelons la CENTER. La valeur par défaut est fichageCourant changera, le label jusqu'à ce que vous le compreniez
fonction DefinirWidgets qui crée tous de centrer. Vous devriez penser à modifiera son texte pour corres- sans faille. Ensuite, nous réglons
les widgets et ensuite nous appe- des points cardinaux. Dans des cir- pondre automatiquement. l'attribut « bind ». Quand nous avons
lons la fonction PlacerWidgets, qui constances normales, les valeurs réel- fait des boutons dans les exemples
les place réellement dans la fenêtre lement utilisables sont E (à droite), W Ci-dessus, nous allons créer quelques- précédents, nous avons utilisé l'attri-
racine. (à gauche), et CENTER (pour centrer). uns des boutons. but « command= » pour définir quelle
fonction serait appelée lorsque l'uti-
def Ensuite, nous réglons le relief, qui J'ai montré seulement 4 boutons lisateur clique sur le bouton. Cette
DefinirWidgets(self,principale): est le style visuel de l'étiquette. Les ici. C'est parce que, comme vous fois, nous utilisons l'attribut « .bind »
options autorisées sont FLAT (à pouvez le voir, le code est presque [Ndt : « relier »]. C'est presque la
self.lblAffichage =
programmer en python volume 4 27
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
même chose, mais c'est un moyen
self.btnDash = Button(principale, text = '­',width = 4,height=3)
plus facile de le faire et de trans- self.btnDash.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('SIGNE'))
mettre des informations à la routine self.btnDot = Button(principale, text = '.',width = 4,height=3)
de rappel qui est statique. Notez self.btnDot.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Dec'))
que nous utilisons ici « <ButtonRe-
lease-1> » comme l'élément déclen- Le bouton « btnDash » change le signe de la valeur affichée. 523 devient -523 et -523 devient 523. Le bouton btnDot
cheur de la liaison. Dans ce cas, nous saisit un point décimal. Ces exemples, ainsi que les suivants, utilisent la fonction foncBoutonFonction.
self.btnPlus = Button(principale,text = '+', width = 4, height=3)
voulons nous assurer que l'appel de self.btnPlus.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Ajouter'))
la fonction se fait seulement après self.btnMinus = Button(principale,text = '­', width = 4, height=3)
que l'utilisateur clique ET relâche le self.btnMinus.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Soustraire'))
bouton gauche de la souris. Enfin, self.btnStar = Button(principale,text = '*', width = 4, height=3)
self.btnStar.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Multiplier'))
nous définissons la fonction de rap- self.btnDiv = Button(principale,text = '/', width = 4, height=3)
pel que nous voulons utiliser et ce self.btnDiv.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Diviser'))
que nous allons lui envoyer. Main- self.btnEqual = Button(principale, text = '=')
tenant, ceux d'entre vous qui sont self.btnEqual.bind('<ButtonRelease­1>', lambda e: self.foncBoutonFonction('Egal'))
astucieux (ce qui est bien sûr votre
Voici les quatre boutons pour les fonctions mathématiques.
cas à tous) noteront quelque chose self.btnClear = Button(principale, text = 'EFFACER')
de nouveau : L'appel « lambda e: ». self.btnClear.bind('<ButtonRelease­1>', lambda e: self.foncEffacer())

En Python, nous utilisons Lambda Enfin, voici le bouton Effacer. Il efface bien sûr les variables et l'affichage. Maintenant nous plaçons les widgets avec
pour définir des fonctions anonymes la routine PlacerWidgets. D'abord nous initialisons la grille, puis nous plaçons les widgets dedans. Voici la première
qui apparaîtront à l'interpréteur partie de la routine :
comme des instructions valides. Cela def PlacerWidgets(self,principale):
nous permet de mettre plusieurs principale.grid(column=0,row=0)
self.lblAffichage.grid(column=0,row=0,columnspan = 4,sticky=EW)
morceaux dans une seule ligne de self.btn1.grid(column = 0, row = 1)
code. Pensez-y comme à une mini- self.btn2.grid(column = 1, row = 1)
fonction. Dans ce cas, nous mettons self.btn3.grid(column = 2, row = 1)
en place le nom de la fonction de self.btn4.grid(column = 0, row = 2)
self.btn5.grid(column = 1, row = 2)
rappel et la valeur que nous voulons self.btn6.grid(column = 2, row = 2)
lui envoyer, ainsi que la balise évé- self.btn7.grid(column = 0, row = 3)
nement (e:). Nous parlerons plus en self.btn8.grid(column = 1, row = 3)
détail de Lambda dans un article ulté- self.btn9.grid(column = 2, row = 3)
self.btn0.grid(column = 1, row = 4)
rieur. Pour l'instant, il suffit de suivre
l'exemple.

Je vous ai donné les quatre premiers


programmer en python volume 4 28
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
boutons. Copiez et collez le code ci- self.btnDash.grid(column = 0, row = 4)
dessus pour les boutons de 5 à 9 et self.btnDot.grid(column = 2, row = 4)
pour le bouton 0. Ils sont tous iden- self.btnPlus.grid(column = 3,row = 1)
tiques, à l'exception du nom du bou- self.btnMinus.grid(column = 3, row = 2)
self.btnStar.grid(column = 3, row = 3)
ton et de la valeur que nous en- self.btnDiv.grid(column=3, row = 4)
voyons au rappel. Les prochaines self.btnEqual.grid(column=0,row=5,columnspan = 4,sticky=NSEW)
étapes sont indiquées à droite. self.btnClear.grid(column=0,row=6,columnspan = 4, sticky = NSEW)

La seule chose dont nous n'avons def funcNumButton(self,val):


pas parlé pour l'instant, ce sont les if self.DecimalNext == True:
attributs « columnspan » et « sticky ». self.DecimalCount += 1
self.CurrentValue = self.CurrentValue + (val * (10**­self.DecimalCount))
Comme je l'ai mentionné auparavant, else:
un widget peut s'étendre sur plus self.CurrentValue = (self.CurrentValue * 10) + val
d'une colonne ou une ligne. Dans ce self.DisplayIt()
cas, nous « étirons » le widget éti-
quette sur les quatre colonnes. C'est Comment pouvons-nous gérer cela et 3, nous faisons les choses suivantes : pro-cessus pour les clics sur 1, 2 et 7.
ce que fait l'attribut « columnspan ». dans le code ? Nous avons déjà réglé Lorsque l'utilisateur clique sur la
Il existe également un attribut les rappels pour les touches numé- L'utilisateur clique 5 : 0 * touche « = », nous devons ensuite
« rowspan ». L'attribut « sticky » [Ndt : riques à la fonction foncBoutonNu-
10 + 5 (5)
ajouter les valeurs de « self.ValeurCou-
« collant »] indique au widget où merique. Il y a deux façons de gérer rante » et « self.Va-leurAncienne »,
L'utilisateur clique 6 : 5 *
aligner ses bords. Pensez-y comme cela. Nous pouvons conserver les in- 10 + 6 (56) les afficher, puis effacer les deux
la manière dont le widget se remplit formations saisies comme une chaîne variables pour continuer.
au sein de la grille. En haut à gauche et puis la convertir en nombre quand
L'utilisateur clique 3 : 56 *
vous voyez le reste de nos boutons. 10 + 3 (563)
nous avons besoin, ou bien nous pou- Ci-dessus, voici le code pour commen-
vons le garder comme un nombre cer à définir nos fonctions de rappel.
Avant d'aller plus loin nous allons Bien sûr, nous devons ensuite affi-
tout le temps. Nous allons utiliser cher la variable « self.ValeurCou-
jeter un œil à la façon dont les choses cette dernière méthode. Pour ce La routine « foncBoutonNumerique »
vont fonctionner quand l'utilisateur rante » dans l'étiquette.
faire, nous allons conserver la valeur reçoit la valeur que nous lui passons
appuiera sur les boutons. qui est déjà là (0 quand nous en appuyant sur un bouton. La seule
Ensuite, l'utilisateur clique sur le bou-
commencerons) dans une variable chose qui diffère de l'exemple ci-
Disons que l'utilisateur veut saisir ton « + ». Nous prenons la valeur de
appelée “self.ValeurCourante”, puis dessus est lorsque l'utilisateur appuie
563 + 127 et obtenir la réponse. Il « self.ValeurCourante » et la plaçons
quand un nouveau chiffre arrive, nous sur le bouton de décimale (« . »). Ci-
appuiera ou cliquera (logiquement) dans la variable « self.ValeurAncienne »
prenons la variable, la multiplions dessous, vous verrez que nous uti-
sur 5, puis 6, puis 3, puis le « + », puis et réinitialisons “self.ValeurCourante”
par 10 et ajoutons la nouvelle valeur. à 0. Nous devons ensuite répéter le lisons une variable booléenne pour
1, puis 2, puis 7, puis le bouton « = ». Ainsi, lorsque l'utilisateur entre 5, 6 retenir le fait qu'il a déjà appuyé sur
programmer en python volume 4 29
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
le bouton décimal, et, au prochain def foncBoutonFonction(self,fonction):
if fonction == 'Dec':
clic, on s'en occupe. D'où la ligne « if self.PartieDecimale = True
self.PartieDecimale == True: ». Nous else:
allons procéder pas à pas. self.PartieDecimale = False
self.CompteDecimales = 0
if fonction == 'SIGNE':
L'utilisateur clique sur 3, puis 2, puis self.ValeurCourante *= ­1
le point décimal, puis 4 pour créer self.Rafraichir()4
« 32.4 ». Nous traitons les clics sur 3 La fonction ABS fournit simplement la valeur actuelle et la multiplie par -1.
et 2 grâce à la routine elif fonction == 'Ajouter':
«  foncBoutonNumerique  ». Nous self.ValeurAncienne = self.ValeurCourante
self.ValeurCourante = 0
vérifions pour voir si self.FonctionCourante = 'Ajouter'
self.PartieDecimale est vrai (ce qu'il La fonction Add copie “self.CurrentValue” dans “self.HolderValue”, nettoie “self.CurrentValue”, and sets the
n'est pas tant que l'utilisateur n'a “self.CurrentFunction” to “Add”. Les fonctions Soustraire, Multiplier et Diviser font la même chose avec the proper
pas cliqué sur le bouton « . »). Sinon, keyword being set in “self.CurrentFunction”.
nous multiplions simplement la elif fonction == 'Soustraire':
valeur de self.ValeurCourante par self.ValeurAncienne = self.ValeurCourante
10 et ajoutons la nouvelle valeur. self.ValeurCourante = 0
self.FonctionCourante = 'Soustraire'
Lorsque l'utilisateur clique sur le elif fonction == 'Multiplier':
«  .  », la fonction de rappel self.ValeurAncienne = self.ValeurCourante
« foncBoutonFonction » est appelée self.ValeurCourante = 0
avec la valeur «  Dec  ». Tout ce que self.FonctionCourante = 'Multiplier'
elif fonction == 'Diviser':
nous faisons est de régler la variable self.ValeurAncienne = self.ValeurCourante
booléenne « self.PartieDecimale » à self.ValeurCourante = 0
vrai (True). Lorsque l'utilisateur self.FonctionCourante = 'Diviser'
clique sur le 4, nous allons tester la La fonction “Eq” (Egal) is where the “magic” happens. Il sera facile pour vous de comprendre le code suivant
valeur de « self.PartieDecimale » et, maintenant.
puisqu'elle est vraie, nous faisons un elif fonction == 'Egal':
if self.FonctionCourante == 'Ajouter':
peu de magie. Premièrement, on self.ValeurCourante += self.ValeurAncienne
incrémente la variable elif self.FonctionCourante == 'Soustraire':
self.CompteDecimales, qui nous self.ValeurCourante = self.ValeurAncienne ­ self.ValeurCourante
indique le nombre de décimales elif self.FonctionCourante == 'Multiplier':
self.ValeurCourante *= self.ValeurAncienne
avec lequel nous travaillons. Nous elif self.FonctionCourante == 'Diviser':
prenons ensuite la nouvelle valeur self.ValeurCourante = self.ValeurAncienne / self.ValeurCourante
entrante, la multiplions par (10**</ self.Rafraichir()
nowiki>-self.CompteDecimales). En self.ValeurCourante = 0
self.ValeurAncienne = 0
programmer en python volume 4 30
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 25
utilisant cet opérateur magique, nous Les prochaines étapes sont indiquées Les exemples 1, 2 et 3 sont ici :
obtenons une simple fonction « élé- sur la page précédente (encadré de http://pastebin.com/RAF4KK6E
vation à la puissance ». Par exemple droite). et l'exemple Calc.py est ici :
10<nowiki>**2 renvoie 100. 10<no- http://pastebin.com/Pxr0H8FJ
wiki></ nowiki>-2 retourne 0.01. La routine «  Rafraichir  » règle sim-
Parfois, en utilisant cette routine, plement la valeur de l'étiquette d'affi- Le mois prochain, nous allons conti-
cela conduit à un problème d'arrondi, chage. N'oubliez pas que nous avons nuer à explorer Tkinter et la ri-
mais pour notre calculatrice simple, dit à l'étiquette de «  surveiller  » la chesse de ses widgets. Dans un
cela fonctionnera pour la plupart des variable « self.AffichageCourant ». À prochain article, nous verrons un
nombres décimaux raisonnables. Je chaque fois que cette variable change, concepteur d'interface graphique pour
vais vous laisser le soin de travailler l'étiquette change automatiquement Tkinter appelé PAGE. En attendant,
à une meilleure fonction. Prenez cela d'affichage pour correspondre. Nous amusez-vous bien. Je pense que vous
comme vos devoirs pour ce mois-ci. utilisons la méthode «  .set  » pour apprécierez Tkinter.
changer la valeur.
def funcClear(self):
def Rafraichir(self):
self.CurrentValue = 0
print('ValeurCourante = {0}
self.HolderValue = 0 ­ ValeurAncienne =
{1}'.format(self.ValeurCoura
self.DisplayIt() nte,self.ValeurAncienne))

La routine « foncEffacer » efface sim- self.AffichageCourant.set(se


lf.ValeurCourante)
plement les deux variables mémoire,
puis rafraîchit l'affichage. def foncEf-
Enfin, nous avons nos lignes de dé-
facer(self): self.ValeurCourante = 0
marrage.
self.ValeurAncienne = 0 self.Rafrai-
chir() Maintenant les fonctions. Nous if __name__ == '__main__':
avons déjà discuté de ce qui se
passe avec la fonction « Dec ». Nous Demarrage()
l'avons traitée en premier avec l'ins-
truction « if ». Nous allons passer au Maintenant, vous pouvez exécuter
« else » et, dans le cas où la fonction le programme et l'essayer.
est autre, nous effaçons les variables
« self.PartieDecimale » et « self.Comp- Comme toujours, le code de cet ar-
teDecimales ». ticle peut être trouvé sur PasteBin.

programmer en python volume 4 31


TUTORIEL Programmer en Python - Partie 26
Greg Walters

L
e mois dernier, nous avons Les boutons radio servent à faire un 2. Le second con-
parlé de TkInter et de quatre choix parmi plusieurs propositions. Ils tiendra des boutons # widgetdemo1.py
# Labels
des widgets disponibles : la ont aussi deux états, oui ou non. Ce- - c'est plutôt simple from Tkinter import *
fenêtre principale, les fenê- pendant, ils sont groupés ensemble aussi - qui utilisent
tres, les boutons et les étiquettes pour fournir un groupe d'options dont différentes options class Demo:
(ou labels). Je vous ai également dit une seule peut être choisie. Vous pou- de relief. def __init__(self,principale):
self.DefinirVariables()
le mois dernier que je parlerais de la vez avoir plusieurs groupes de boutons 3. Dans ce cadre, f = self.ConstruireWidgets(principale)
façon d'avoir un widget autre que le radio qui, s'ils sont bien programmés, nous aurons deux self.PlacerWidgets(f)
widget de premier niveau comme n'interféreront pas entre eux. cases à cocher et un
parent. Aussi, ce mois-ci, nous allons bouton qui peut les
approfondir les fenêtres, les boutons Une ListBox fournit une liste d'élé- activer/désactiver et qui enverront bouton va effacer la zone de liste et
et les étiquettes, et introduire les ments parmi lesquels l'utilisateur peut leur état (1 ou 0) à la fenêtre du l'autre la remplira avec des valeurs
cases à cocher, les boutons radio, les choisir. La plupart du temps, vous terminal lorsqu'on clique dessus ou fictives.
zones de texte (ou widgets Entry), voulez que l'utilisateur sélectionne les active/désactive. 7. Le dernier cadre contient une série
les listes avec une barre de défile- un seul des éléments à la fois, mais, 4. Ensuite, nous aurons deux grou- de boutons qui appellent les dif-
ment verticale (ListBox) et les fenêtres parfois, vous pouvez vouloir permet- pes de trois boutons radio, chacun férents types de boîtes de message.
de message. Avant de commencer, tre à l'utilisateur de sélectionner plu- envoyant un message à la fenêtre
examinons certains de ces widgets. sieurs éléments. Une barre de défile- du terminal lorsqu'on clique dessus. Bon, maintenant nous allons com-
ment peut être placée horizonta- Chaque groupe est indépendant de mencer notre projet. Nommons-le
Les cases à cocher servent à faire lement ou verticalement afin de per- l'autre. «  wid-getdemo1.py ». Assurez-vous
plusieurs choix parmi plusieurs pro- mettre à l'utilisateur de parcourir facile- 5. Celui-ci contient des champs de de le sauvegarder, car nous allons
positions et ont deux états : cochée ment tous les éléments disponibles. texte qui ne sont pas nouveaux pour écrire notre projet par petits morceaux
ou non cochée, ou on pourrait dire vous, mais il y a aussi un bouton et construire notre application com-
aussi oui ou non. Elles sont géné- Notre projet consistera en une fe- pour activer et désactiver l'un d'eux. plète petit à petit. Chaque morceau
ralement utilisées pour fournir une nêtre principale et sept cadres princi- Lorsqu'il est désactivé, aucune saisie tourne autour de l'un des cadres.
série d'options où une, quelques- paux qui regrouperont visuellement ne peut y être faite. Vous remarquerez que j'intègre un
unes ou toutes peuvent être sélec- nos ensembles de widgets : 6. Celui-ci contient une liste avec certain nombre de commentaires au
tionnées. Vous pouvez définir un une barre de défilement verticale fur et à mesure, pour que vous puis-
événement pour vous informer quand 1. Le premier cadre sera très basique : qui envoie un message au terminal siez suivre ce qui se passe. Voici les
la case a changé d'état ou tout sim- il contient simplement différents labels, chaque fois qu'un élément est sélec- premières lignes (voir encadré ci-
plement pour interroger la valeur montrant les différentes options de tionné ; il aura deux boutons. Un dessus).
du widget à tout moment. relief.
programmer en python volume 4 32
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
Les deux premières lignes (com- def ConstruireWidgets(self,principale):
mentaires) sont le nom de l'appli- # definition de nos widgets
cation et le thème de cette partie. fenetre = Frame(principale)
La ligne trois est notre déclaration # labels (ou etiquettes)
self.fenetreLabels = Frame(fenetre,relief = SUNKEN,padx = 3, pady = 3,
d'importation. Ensuite, nous définis- borderwidth = 2, width = 500)
sons notre classe. La ligne suivante self.lbl1 = Label(self.fenetreLabels,text="Label plat",relief = FLAT,
commence notre routine __init__, avec width = 13,borderwidth = 2)
laquelle vous devriez tous être fami- self.lbl2 = Label(self.fenetreLabels,text="Label creux", relief = SUNKEN,
width = 13, borderwidth = 2)
liers maintenant ; mais si vous venez self.lbl3 = Label(self.fenetreLabels,text="Label arete", relief = RIDGE, width = 13,
juste de nous rejoindre, c'est le code borderwidth = 2)
qui est exécuté quand on instancie self.lbl4 = Label(self.fenetreLabels,text="Label souleve", relief = RAISED,
la routine dans la partie principale width = 13, borderwidth = 2)
self.lbl5 = Label(self.fenetreLabels,text="Label rainure", relief = GROOVE,
du programme. Nous lui passons la fe- width = 13, borderwidth = 2)
nêtre racine (ou toplevel), qui s'ap- return fenetre
pelle « principale » ici. Les trois der-
nières lignes (jusqu'à présent) appellent
trois routines différentes. La pre- ont été coupées pour deux raisons. mettre dans la grille dans une routine que son relief en creux soit percep-
mière (DefinirVariables) réglera diffé- Tout d'abord, c'est une bonne pra- comme celle-ci facilite les choses, tible. Par défaut, la largeur de bor-
rentes variables dont nous aurons tique de garder la longueur de la puisque nous faisons (la plupart) des dure vaut 0 et l'effet de creux ne
besoin plus tard. La suivante (Cons- ligne à moins de 80 caractères. Deuxiè- définitions dans cette routine. serait pas visible. Enfin, nous avons
truireWidgets) sera l'endroit où nous mement, cela facilite les choses mis la largeur totale de la fenêtre à
définissons nos widgets, et la der- pour notre merveilleux éditeur. Nous définissons donc d'abord notre 500 pixels.
nière (PlacerWidgets) est celle où Vous avez deux possibilités : soit fenêtre principale. C'est là que nous
nous allons placer les widgets dans écrire des lignes longues, soit les mettrons le reste de nos widgets. Ensuite, nous définissons chaque wid-
la fenêtre racine. Comme nous l'avons garder comme ça. Python nous permet Ensuite, nous définissons une fenêtre get étiquette que nous allons uti-
fait la dernière fois, nous allons uti- de couper les lignes tant qu'elles fille (de la fenêtre principale), qui liser. Nous fixons le parent à self.fene-
liser le gestionnaire de géométrie sont dans des parenthèses ou des contiendra cinq étiquettes, et l'ap- treLabels, et non pas fenetre. De
« grille ». Notez que ConstruireWid- crochets. Comme je l'ai dit précé- pelons fenetreLabels. Nous réglons cette façon, toutes les étiquettes sont
gets retournera l'objet « f » (qui est demment, nous définissons les widgets les différents attributs de la fenêtre des enfants de fenetreLabels et fe-
notre fenêtre racine) et que nous le avant de les placer dans la grille. ici. Nous réglons le relief à « en creux » netreLabels est un enfant de fenetre.
passerons à la routine PlacerWidgets. Quand nous écrirons la routine sui- (« SUNKEN »), un remplissage de 3 Remarquez que chaque définition est
vante, vous remarquerez que nous pixels à gauche et à droite (padx), et à peu près semblable pour l'ensemble
Voici notre routine ConstruireWid- pouvons aussi définir un widget au de 3 pixels en haut et en bas (pady). des cinq étiquettes, sauf le nom du
gets (ci-contre, en haut à droite). Les moment où nous le plaçons dans la Nous avons également mis la largeur widget (lbl1, lbl2, etc), le texte et le
lignes qui commencent par « self. » grille, mais le définir avant de le de bordure à 2 pixels de telle sorte relief ou l'effet visuel. Enfin, nous retour-
programmer en python volume 4 33
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
nons la fenêtre à la routine appelante maintenant. Nous avons mis def PlacerWidgets(self, principale):
(_init_). comme parent fenetreLa- fenetre = principale
bels, tout comme les autres # place les widgets
Voici notre routine PlacerWidgets étiquettes. Nous réglons le fenetre.grid(column = 0, row = 0)
# place les labels
(page suivante, en haut à droite). texte à « 'La-bels |' », la largeur self.fenetreLabels.grid(column = 0, row = 1, padx = 5, pady = 5,
à 15, et l'ancre à Est ('e'). Si columnspan = 5,sticky='WE')
Nous récupérons l'objet fenêtre en vous vous souvenez de la l = Label(self.fenetreLabels,text='Labels |',width=15,
tant que paramètre appelé « princi- dernière fois, en utilisant l'attri- anchor='e').grid(column=0,row=0)
self.lbl1.grid(column = 1, row = 0, padx = 3, pady = 5)
pale ». Nous l'assignons à « fenetre » but d'ancrage, nous pouvons self.lbl2.grid(column = 2, row = 0, padx = 3, pady = 5)
simplement pour être cohérent avec choisir où le texte s'affiche self.lbl3.grid(column = 3, row = 0, padx = 3, pady = 5)
ce que nous avons fait dans la routine dans le widget. Dans ce cas, self.lbl4.grid(column = 4, row = 0, padx = 3, pady = 5)
ConstruireWidgets. Ensuite, nous met- c'est le long du bord droit. self.lbl5.grid(column = 5, row = 0, padx = 3, pady = 5)
tons en place la grille principale (fe- Maintenant la partie amusante.
netre.grid (column=0, row=0)). Si nous Ici, nous définissons l'emplace-
ne faisons pas cela, rien ne fonc- ment de la grille (et tous les root = Tk() notre objet Demo et, enfin, appelons
root.geometry('750x40+150+150') la boucle principale de Tk.
tionnera correctement. Ensuite, nous autres attributs de la grille dont nous root.title("Widget Demo 1")
commençons à mettre nos widgets avons besoin), simplement en ajoutant demo = Demo(root)
dans les emplacements de la grille. « .grid » à la fin de la définition des root.mainloop() Essayez. Vous devriez voir les cinq
D'abord nous mettons la fenêtre étiquettes. étiquettes ainsi que l'étiquette de
(fenetreLabels) qui contient toutes D'abord, on initialise une instance « dernière minute » avec divers effets
nos étiquettes et définissons ses Ensuite, nous plaçons toutes nos autres de Tk. Puis nous définissons la taille magnifiques.
attributs. Nous la plaçons colonne 0, étiquettes dans la grille, à partir de de la fenêtre principale à 750 pixels
ligne 1, réglons le remplissage à 5 la colonne 1, ligne 0. de large sur 40 pixels de haut et la Les boutons
pixels sur tous les côtés, lui disons localisons à 150 pixels de la gauche
de s'étaler sur 5 colonnes (à droite Voici notre routine DefinirVariables. et du haut de l'écran. Puis nous réglons Maintenant, enregistrez ce que vous
et à gauche), et enfin utilisons l'attri- Notez que nous utilisons simplement le titre de la fenêtre et instancions avez en tant que widgetde-mo1a.py
but « sticky » [Ndt : collant] pour forcer l'instruction pass pour l'instant. Nous
la fenêtre à s'étendre complètement la remplirons plus tard, car nous n'en # place les boutons
self.fenetreBoutons.grid(column=0, row = 2, padx = 5,
à gauche et à droite (« WE » pour avons pas besoin pour cette partie :
pady = 5, columnspan = 5,sticky = 'WE')
Ouest et Est). Maintenant vient la l = Label(self.fenetreBoutons,text='Boutons |',width=15,
partie qui enfreint la règle dont je def DefinirVariables(self): anchor='e').grid(column=0,row=0)
# Definit nos ressources
vous ai parlé. Nous mettons une éti- pass
self.btn1.grid(column = 1, row = 0, padx = 3, pady = 3)
self.btn2.grid(column = 2, row = 0, padx = 3, pady = 3)
quette comme premier widget dans self.btn3.grid(column = 3, row = 0, padx = 3, pady = 3)
la fenêtre, mais nous ne l'avons pas Et enfin nous plaçons notre code self.btn4.grid(column = 4, row = 0, padx = 3, pady = 3)
défini à l'avance : nous le définissons pour la routine principale : self.btn5.grid(column = 5, row = 0, padx = 3, pady = 3)
programmer en python volume 4 34
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
et nous allons ajouter quelques bou- cliqué. La principale
tons. Puisque nous avons construit chose à regarder ici (lors- # boutons
self.fenetreBoutons = Frame(fenetre,relief = SUNKEN,padx = 3,
notre programme de base ainsi, nous que nous exécutons pady = 3, borderwidth = 2, width = 500)
allons simplement pouvoir y ajouter le programme) est que self.btn1 = Button(self.fenetreBoutons,text="Bouton plat",
les parties qui manquent. Commençons le bouton « en creux » relief = FLAT, borderwidth = 2)
par la routine ConstruireWidgets. Après ne bouge pas lorsqu'on self.btn2 = Button(self.fenetreBoutons,text="Bouton creux",
relief = SUNKEN, borderwidth = 2)
les définitions des étiquettes, et clique dessus. En gé- self.btn3 = Button(self.fenetreBoutons,text="Bouton arete",
avant le « return fenetre », ajoutez néral on n'utilise pas relief = RIDGE, borderwidth = 2)
ce qui se trouve en haut de la page le relief « en creux », self.btn4 = Button(self.fenetreBoutons,text="Bouton souleve",
suivante. sauf si vous souhaitez relief = RAISED, borderwidth = 2)
self.btn5 = Button(self.fenetreBoutons,text="Bouton rainure",
Rien de bien nouveau ici. Nous avons un bouton qui reste relief = GROOVE, borderwidth = 2)
défini les boutons avec leurs attri- enfoncé lorsque vous self.btn1.bind('<ButtonRelease­1>',lambda e: self.clicBouton(1))
buts et avons fixé leurs fonctions de cliquez dessus. Enfin, self.btn2.bind('<ButtonRelease­1>',lambda e: self.clicBouton(2))
rappel avec un « .bind ». Notez que nous avons besoin self.btn3.bind('<ButtonRelease­1>',lambda e: self.clicBouton(3))
self.btn4.bind('<ButtonRelease­1>',lambda e: self.clicBouton(4))
nous utilisons lambda pour envoyer d'ajuster la déclaration self.btn5.bind('<ButtonRelease­1>',lambda e: self.clicBouton(5))
les valeurs 1 à 5 suivant le bouton delagéométrieàcause
sur lequel on clique. Dans la fonction des widgets supplé-
de rappel, nous allons utiliser cela mentaires que nous normale, à laquelle vous pouvez enfoncé. Nous pouvons faire cela
afin de savoir quel bouton on doit avons ajoutés : vous attendre. La seconde est plus simplement en définissant l'attribut
gérer. Maintenant, nous allons travailler comme un « bouton collant » - quand indicatoron à False. Le bouton « nor-
dans la routine PlacerWidgets. Placez root.geometry('750x110+150+150') elle n'est pas sélectionnée (ou mal » permet de basculer les cases
le code (page précédente, en bas à cochée), elle ressemble à un bouton de « cochées » à «  décochées » et
droite) juste après l'emplacement de Ok. C'est terminé pour celui-ci. Enregis- normal. Lorsque vous la sélectionnez, vice- versa, à chaque fois que l'on
la dernière étiquette. trez-le et lancez-le. elle ressemble à un bouton qui reste clique dessus. Nous arrivons à pro-

Une fois de plus, rien de vraiment Maintenant sauvegardez ceci comme def clicBouton(self,val):
nouveau ici, donc nous allons conti- widgetdemo1b.py et nous allons passer if val == 1:
nuer. Voici notre routine de rappel aux cases à cocher. print("Clic bouton plat...")
elif val == 2:
(ci-contre, en bas à droite). Placez-la print("Clic bouton creux...")
après la routine DefinirVariables. Les cases à cocher elif val == 3:
print("Clic bouton arete...")
Encore une fois, rien de vraiment Comme je l'ai dit précédemment, elif val == 4:
print("Clic bouton souleve...")
sensationnel ici. Nous utilisons simple- cette partie de la démo a un bouton elif val == 5:
ment une série de routines IF/ELIF normal et deux cases à cocher. L'appa- print("Clic bouton rainure...")
pour afficher quel bouton a été rence de la première case est celle,
programmer en python volume 4 35
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
grammer cela en appelant la méthode self.Chk1Val =
.toggle liée à la case à cocher. Nous IntVar()
self.Chk2Val = # checkbox (ou cases a cocher)
relions l'événement clic gauche de la IntVar() self.fenetreCases = Frame(fenetre, relief = SUNKEN, padx = 3, pady = 3,
souris (lorsque le bouton est relâché) borderwidth = 2, width = 500)
à une fonction afin de pouvoir en- Après la fonction de rap- self.chk1 = Checkbutton(self.fenetreCases, text = "Case a cocher normale",
voyer un message (dans notre cas) pel des boutons, placez variable=self.Chk1Val)
self.chk2 = Checkbutton(self.fenetreCases, text = "Case a cocher",
au terminal. En plus de tout cela, ce qui suit (ci-contre, variable=self.Chk2Val,indicatoron = False)
nous mettons en place deux variables en bas à droite). self.chk1.bind('<ButtonRelease­1>',lambda e: self.clicCases(1))
(une pour chacune des cases à cocher) self.chk2.bind('<ButtonRelease­1>',lambda e: self.clicCases(2))
que l'on peut interroger à tout mo- Et enfin remplacez l'ins- self.btnInverserCases = Button(self.fenetreCases,text="Inverser cases")
self.btnInverserCases.bind('<ButtonRelease­1>',self.btnInverser)
ment. Ici, nous interrogeons ces truction de géométrie
valeurs et les affichons à chaque fois par ceci :
qu'une case est cliquée. Faites atten-
tion à la partie variable du code : elle root.geometry('75
est utilisée dans de nombreux widgets. 0x170+150+150')
# place les cases à cocher et le bouton d'inversion
Dans la routine ConstruireWidgets, self.fenetreCases.grid(column = 0, row = 3, padx = 5, pady = 5,
après le code des boutons que nous Enregistrez et exécutez. columnspan = 5,sticky = 'WE')
venons d'ajouter et avant l'instruction Enregistrez-lecommewid- l = Label(self.fenetreCases,text='Cases à cocher |',width=15,
de retour, placez le code (ci-contre, getdemo1c.py et conti- anchor='e').grid(column=0,row=0)
self.btnInverserCases.grid(column = 1, row = 0, padx = 3, pady = 3)
en haut à droite). nuons avec les boutons
radio. self.chk1.grid(column = 2, row = 0, padx = 3, pady = 3)
Encore une fois, vous avez vu tout self.chk2.grid(column = 3, row = 0, padx = 3, pady = 3)
cela avant. Nous créons la fenêtre
pour contenir nos widgets. Nous
créons un bouton et deux cases à def btnInverser(self,p1):
cocher. Plaçons-les maintenant (ci- self.chk1.toggle()
contre, au milieu à droite). self.chk2.toggle()
print("Valeur de la case à cocher 1 : {0}".format(self.Chk1Val.get()))
print("Valeur de la case à cocher 2 : {0}".format(self.Chk2Val.get()))
Maintenant, nous définissons les deux
variables que nous allons utiliser
pour surveiller la valeur de chaque
case à cocher. Sous DefinirVariables,
commentez l'instruction pass et
ajoutez ceci :

programmer en python volume 4 36


TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26

Les boutons radio drez pourquoi on appelle cela des riable nommée self.RBVal. Le second cliqués.
boutons radio. Lorsque vous utilisez groupe est formé par la variable
Si vous êtes assez vieux pour vous des boutons radio, l'attribut variable self.RBVal2. Nous devons également Retournez dans ConstruireWidgets,
souvenir des autoradios avec boutons est très important. C'est ce qui re- définir l'attribut « value » au moment et ajoutez le code (ci-dessous), juste
poussoirs pour sélectionner les sta- groupe les boutons radio ensemble. de la conception, afin de garantir avant l'instruction de retour.
tions pré-réglées, vous compren- Dans cette démo, le premier groupe que les boutons retourneront une
de boutons est formé avec la va- valeur qui a du sens quand ils seront

# boutons radio
self.fenetreBoutonsRadio = Frame(fenetre, relief = SUNKEN, padx = 3, pady = 3, borderwidth = 2, width = 500)
self.rb1 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 1", variable = self.RBVal, value = 1)
self.rb2 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 2", variable = self.RBVal, value = 2)
self.rb3 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 3", variable = self.RBVal, value = 3)
self.rb1.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio())
self.rb2.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio())
self.rb3.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio())
self.rb4 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 4", variable = self.RBVal2, value = "1­1")
self.rb5 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 5", variable = self.RBVal2, value = "1­2")
self.rb6 = Radiobutton(self.fenetreBoutonsRadio, text = "Radio 6", variable = self.RBVal2, value = "1­3")
self.rb4.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio2())
self.rb5.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio2())
self.rb6.bind('<ButtonRelease­1>',lambda e: self.clicBoutonRadio2())

Ajoutez ceci dans PlacerWidgets :

# place les boutons radio et selectionne le premier


self.fenetreBoutonsRadio.grid(column = 0, row = 4, padx = 5, pady = 5, columnspan = 5,sticky = 'WE')
l = Label(self.fenetreBoutonsRadio,
text='Boutons radio |',
width=15,anchor='e').grid(column=0,row=0)
self.rb1.grid(column = 2, row = 0, padx = 3, pady = 3, sticky = 'EW')
self.rb2.grid(column = 3, row = 0, padx = 3, pady = 3, sticky = 'WE')
self.rb3.grid(column = 4, row = 0, padx = 3, pady = 3, sticky = 'WE')
self.RBVal.set("1")
l = Label(self.fenetreBoutonsRadio,text='| Un autre groupe |',
width = 15,
anchor = 'e').grid(column = 5, row = 0)
self.rb4.grid(column = 6, row = 0)
self.rb5.grid(column = 7, row = 0)
self.rb6.grid(column = 8, row = 0)
self.RBVal2.set("1­1")
programmer en python volume 4 37
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
Une chose à noter ici. Remarquez les Les champs
définitions de « dernière minute » # champs de texte
pour les étiquettes dans la routine de texte self.fenetreChampsTexte = Frame(fenetre, relief = SUNKEN, padx
= 3, pady = 3, borderwidth = 2, width = 500)
PlacerWidgets. Ces lignes longues
sont coupées pour montrer comment Encore une fois, nous self.txt1 = Entry(self.fenetreChampsTexte, width = 10)
self.txt2 = Entry(self.fenetreChampsTexte,
utiliser les parenthèses pour permet- avons déjà utilisé des disabledbackground="#cccccc", width = 10)
tre à nos longues lignes d'être for- champs de texte (ou self.btnDesactiver = Button(self.fenetreChampsTexte, text =
matées correctement dans notre code, widgets de saisie) dans "Activer/Desactiver")
et de fonctionner toujours correc- diverses interfaces gra- self.btnDesactiver.bind('<ButtonRelease­1>',
self.clicBoutonDesactiver)
tement. phiques auparavant.
Maiscettefois-ci,comme
Ensuite, ajoutez ces lignes de code à la routine PlacerWidgets :
Dans DefinirVariables, ajoutez : je l'ai dit précédem- # place les champs de texte
ment, nous allons mon- self.fenetreChampsTexte.grid(column = 0, row = 5, padx = 5,
self.RBVal = IntVar() trer comment empêcher pady = 5, columnspan = 5,sticky = 'WE')
l'utilisateur de faire des l = Label(self.fenetreChampsTexte,text='Champs de texte
|',width=15, anchor='e').grid(column=0,row=0)
Ajoutez les routines de clics : changements dans le self.txt1.grid(column = 2, row = 0, padx = 3, pady = 3)
champ de texte en le self.txt2.grid(column = 3, row = 0, padx = 3, pady = 3)
def clicBoutonRadio(self): désactivant. Cela s'avère self.btnDesactiver.grid(column = 1, row = 0, padx = 3, pady = 3)
print("Clic bouton
radio ­ Valeur : utile si vous affichez
{0}".format(self.RBVal.get())) certaines données et Ajoutez cette ligne en bas de la routine DefinirVariables :
permettez à l'utilisateur self.Disabled = False
def clicBoutonRadio2 de les modifier seule-
(self):
ment quand il est dans Maintenant ajoutez la fonction qui répond au clic sur le bouton :
print("Clic bouton def clicBoutonDesactiver(self,p1):
radio ­ Valeur : un mode d'édition. if self.Disabled == False:
{0}".format(self.RBVal2.get())) Maintenant vous devriez self.Disabled = True
savoir que la première self.txt2.configure(state='disabled')
et enfin modifiez à nouveau la géo- chose que nous devons else:
métrie comme ceci : faire est d'ajouter du
self.Disabled = False
self.txt2.configure(state='normal')
code à la routine Cons-
root.geometry('750x220+150+150')
truireWidgets (ci-contre, Enfin, relancez la ligne de géométrie ::
à droite). root.geometry('750x270+150+150')
Enregistrez le projet sous widgetde-
mo1d.py et exécutez-le. Maintenant,
Comme d'habitude, nous Sauvegardez-le sous le nom widgetdemo1d.py et exécutez-le.
nous allons travailler sur les champs
créons notre fenêtre.
de texte standard (ou widgets de
Puis nous créons notre
saisie).
programmer en python volume 4 38
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
barre de défilement verticale. Nous ajoutez (ci-contre, en # champs de texte
faisons cela avant de créer la liste, bas à droite). self.fenetreChampsTexte = Frame(fenetre, relief = SUNKEN, padx
parce que nous devons faire réfé- = 3, pady = 3, borderwidth = 2, width = 500)
rence à la méthode «  .set » de la Voici la routine d'ap- self.txt1 = Entry(self.fenetreChampsTexte, width = 10)
self.txt2 = Entry(self.fenetreChampsTexte,
barre de défilement. Remarquez pui (ci-contre, en disabledbackground="#cccccc", width = 10)
l'attribut « height = 5 ». Cela force la haut à droite). Pour self.btnDesactiver = Button(self.fenetreChampsTexte, text =
liste à montrer 5 éléments à la fois. les trois premiers "Activer/Desactiver")
Dans la déclaration .bind, nous utili- (Info, Avertissement self.btnDesactiver.bind('<ButtonRelease­1>',
self.clicBoutonDesactiver)
sons « "ListboxSelect" » comme événe- et Erreur), il suffit
ment. C'est ce qu'on appelle un évé- d'appeler « tkMessa-
Ensuite, ajoutez ces lignes de code à la routine PlacerWidgets :
nement virtuel, puisque ce n'est pas geBox.show-info », ou # place les champs de texte
vraiment un événement « officiel ». celui dont vous avez self.fenetreChampsTexte.grid(column = 0, row = 5, padx = 5,
besoin, avec deux pa- pady = 5, columnspan = 5,sticky = 'WE')
Maintenant, nous allons nous occu- ramètres. Le premier l = Label(self.fenetreChampsTexte,text='Champs de texte
|',width=15, anchor='e').grid(column=0,row=0)
per du code supplémentaire dans la est le titre de la self.txt1.grid(column = 2, row = 0, padx = 3, pady = 3)
routine PlacerWidgets (page suivante, boîte de message et self.txt2.grid(column = 3, row = 0, padx = 3, pady = 3)
encadré de gauche). le second est le mes- self.btnDesactiver.grid(column = 1, row = 0, padx = 3, pady = 3)
sage réel que vous
voulez montrer. L'icône Ajoutez cette ligne en bas de la routine DefinirVariables :
Les boîtes de dialogue self.Disabled = False
est gérée pour vous
Cette section est tout simplement par Tkinter. Pour les
dialogues qui fournis- Maintenant ajoutez la fonction qui répond au clic sur le bouton :
une série de boutons « normaux » def clicBoutonDesactiver(self,p1):
qui appellent les différents types de sent une réponse if self.Disabled == False:
boîtes de dialogue. Nous les avons (Question, Oui/Non), self.Disabled = True
déjà rencontrés avec une boîte à outils nous fournissons une self.txt2.configure(state='disabled')
variable qui reçoit la else:
différente. Nous allons explorer self.Disabled = False
seulement 5 types différents, mais il valeur correspondant self.txt2.configure(state='normal')
y en a plus. Dans cette section, nous au bouton cliqué.
allons regarder Information, Avertis- Dans le cas de la Enfin, relancez la ligne de géométrie ::
sement, Erreur, Question, et les dia- boîte de dialogue root.geometry('750x270+150+150')
logues Oui/Non. Ils sont très utiles Question, la réponse
lorsque vous avez besoin de faire est « Oui » ou « Non », Sauvegardez-le sous le nom widgetdemo1d.py et exécutez-le.
passer des informations à votre utili- et dans le cas du dia-
sateur d'une manière assez importante. logue Oui/Non, la ré-
Dans la routine ConstruireWidgets, ponse est « True »
programmer en python volume 4 39
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
# place la liste et les boutons associes # des choses pour la liste
self.fenetreListe.grid(column = 0, row = 6, padx = 5, self.fenetreListe = Frame(fenetre,
pady = 5, columnspan = 5,sticky = 'WE') relief = SUNKEN,
l = Label(self.fenetreListe,text='Liste |',width=15, padx = 3,
anchor='e').grid(column=0,row=0,rowspan=2) pady = 3,
self.liste.grid(column = 2, row = 0,rowspan=2) borderwidth = 2,
self.defilementV.grid(column = 3, row = 0,rowspan = width = 500
2, sticky = 'NSW') )
self.btnEffacerListe.grid(column = 1, row = 0, padx = 5) # boite avec barre de défilement pour la
self.btnRemplirListe.grid(column = 1, row = 1, padx = 5) liste
self.defilementV =
Scrollbar(self.fenetreListe)
Ajoutez ceci dans DefinirVariables : self.liste = Listbox(self.fenetreListe,
# les elements pour notre liste height = 5,
self.exemples = ['Element un','Element deux','Element yscrollcommand =
trois','Element quatre'] self.defilementV.set)
# hauteur par defaut = 10
Et ajoutez les routines de support suivantes :
def effacerListe(self): self.liste.bind('<<ListboxSelect>>',self.listeSelec
self.liste.delete(0,END) tion)
self.defilementV.config(command =
def remplirListe(self): self.liste.yview)
# Note : effacer d'abord la liste ; aucune self.btnEffacerListe = Button(
verification n'est faite self.fenetreListe,
for ex in self.exemples: text = "Effacer liste",
self.liste.insert(END,ex) command = self.effacerListe,
# insert([0,ACTIVE,END],element) width = 11
)
def listeSelection(self,p1): self.btnRemplirListe = Button(
print("Clic sur un élément de la liste") self.fenetreListe,
items = self.liste.curselection() text = "Remplir liste",
selitem = items[0] command = self.remplirListe,
print("Index de l'élément choisi : width = 11
{0}".format(selitem)) )
print("Texte de l'élément choisi : # <<ListboxSelect>> est un evenement virtuel
{0}".format(self.liste.get(selitem))) # remplit la liste
self.remplirListe()
Enfin, mettez à jour la ligne de géométrie :
root.geometry('750x370+150+150')

Sauvegarder cela comme widgetdemo1e.py et exécutez-le. Maintenant, nous


allons faire les dernières modifications à notre application.
programmer en python volume 4 40
TUTORIEL - PROGRAMMER EN PYTHON - PARTIE 26
ou « False ». getdemo1f.py et amusez-vous avec. C'est tout pour cette fois. J'espère
Enfin, modifiez la ligne de géo- que ceci vous aura inspiré à explorer
métrie : J'ai placé le code de widgetde- toutes les fonctionnalités proposées
root.geometry('750x490+550+150') mo1f.py sur pastebin ici : par tkinter. À la prochaine fois.
http://pastebin.com/TQgppVnF
Sauvegardez ceci sous le nom wid-
def afficheFenetreMessage(self,which):
if which == 1:
tkMessageBox.showinfo('Demo','Voici un message INFO')
elif which == 2:
tkMessageBox.showwarning('Demo','Voici un message WARNING (avertissement)')
elif which == 3:
tkMessageBox.showerror('Demo','Voici un message ERREUR')
elif which == 4:
rep = tkMessageBox.askquestion('Demo','Voici une QUESTION ?')
print('clic sur {0}...'.format(rep))
elif which == 5:
rep = tkMessageBox.askyesno('Demo','Voici un message OUI/NON')
print('clic sur {0}...'.format(rep))
# boutons pour afficher les fenetres de messages et de dialogues
self.fenetreMessages = Frame(fenetre,relief = SUNKEN,padx = 3, pady = 3, borderwidth = 2)
self.btnMBInfo = Button(self.fenetreMessages,text = "Info")
self.btnMBWarning = Button(self.fenetreMessages,text = "Avertissement")
self.btnMBError = Button(self.fenetreMessages,text = "Erreur")
self.btnMBQuestion = Button(self.fenetreMessages,text = "Question")
self.btnMBYesNo = Button(self.fenetreMessages,text = "Oui/Non")
self.btnMBInfo.bind('<ButtonRelease­1>', lambda e: self.afficheFenetreMessage(1))
self.btnMBWarning.bind('<ButtonRelease­1>', lambda e: self.afficheFenetreMessage(2))
self.btnMBError.bind('<ButtonRelease­1>', lambda e: self.afficheFenetreMessage(3))
self.btnMBQuestion.bind('<ButtonRelease­1>', lambda e: self.afficheFenetreMessage(4))
self.btnMBYesNo.bind('<ButtonRelease­1>', lambda e: self.afficheFenetreMessage(5))

Maintenant ajoutez le code dans la routine PlacerWidgets :


# boutons de boîtes de messages et de dialogues
self.fenetreMessages.grid(column = 0,row = 7, columnspan = 5, padx = 5, sticky = 'WE')
l = Label(self.fenetreMessages,text='Messages |',width=15, anchor='e').grid(column=0,row=0)
self.btnMBInfo.grid(column = 1, row = 0, padx= 3)
self.btnMBWarning.grid(column = 2, row = 0, padx= 3)
self.btnMBError.grid(column = 3, row = 0, padx= 3)
self.btnMBQuestion.grid(column = 4, row = 0, padx= 3)
self.btnMBYesNo.grid(column = 5, row = 0, padx= 3)
programmer en python volume 4 41

Vous aimerez peut-être aussi