Une introduction à Python 3

Image non disponible


précédentsommairesuivant

8. La POO graphique

Image non disponible

Très utilisées dans les systèmes d'exploitation et dans les applications, les interfaces graphiques sont programmables en Python.

Parmi les différentes bibliothèques graphiques utilisables dans Python (GTK+, wxPython, Qt…), la bibliothèque tkinter, issue du langage tcl/Tk est installée de base dans toutes les distributions Python. tkinter facilite la construction d'interfaces graphiques simples.

Après avoir importé la bibliothèque, la démarche consiste à créer, configurer et positionner les éléments graphiques (widgets) utilisés, à coder les fonctions/méthodes associées aux widgets, puis d'entrer dans une boucle chargée de récupérer et traiter les différents événements pouvant se produire au niveau de l'interface graphique : interactions de l'utilisateur, besoins de mises à jour graphiques, etc.

8-1. Programmes pilotés par des événements

En programmation graphique objet, on remplace le déroulement séquentiel du script par une boucle d'événements (Fig. 8.1)

Image non disponible
Figure 8.1 - Deux styles de programmation

8-2. La bibliothèque tkinter

8-2-1. Présentation

C'est une bibliothèque assez simple qui provient de l'extension graphique, Tk, du langage Tcl(27). Cette extension a largement essaimé hors de Tcl/Tk et on peut l'utiliser en Perl, Python, Ruby, etc. Dans le cas de Python, l'extension a été renommée tkinter.

Parallèlement à Tk, des extensions ont été développées dont certaines sont utilisées en Python. Par exemple le module standard Tix met une quarantaine de widgets à la disposition du développeur.

De son côté, le langage Tcl/Tk a largement évolué. La version 8.5 actuelle offre une bibliothèque appelée Ttk qui permet d'habiller les widgets avec différents thèmes ou styles. Ce module est également disponible à partir de Python 3.1.1.

8-2-1-a. Un exemple tkinter simple (Fig. 8.2)

 
Sélectionnez
import tkinter

# création d'un widget affichant un simple message textuel
widget = tkinter.Label(None, text='Bonjour monde graphique !')
widget.pack() # positionnement du label
widget.mainloop() # lancement de la boucle d'événements
Image non disponible
Figure 8.2 - Un exemple simple : l'affichage d'un Label

8-2-2. Les widgets de tkinter

On appelle widget (mot valise, contraction de window et gadget) les composants graphiques de base d'une bibliothèque.

Liste des principaux widgets de tkinter :

  • Tk fenêtre de plus haut niveau ;
  • Frame contenant pour organiser d'autres widgets ;
  • Label zone de message ;
  • Button bouton d'action avec texte ou image ;
  • Message zone d'affichage multiligne ;
  • Entry zone de saisie ;
  • Checkbutton bouton à deux états ;
  • Radiobutton bouton à deux états exclusifs par groupe de boutons ;
  • Scale glissière à plusieurs positions ;
  • PhotoImage sert à placer des images (GIF et PPM/PGM) sur des widgets ;
  • BitmapImage sert à placer des bitmaps (X11 bitmap data) sur des widgets ;
  • Menu menu déroulant associé à un Menubutton ;
  • Menubutton bouton ouvrant un menu d'options ;
  • Scrollbar ascenseur ;
  • Listbox liste à sélection pour des textes ;
  • Text édition de texte simple ou multiligne ;
  • Canvas zone de dessins graphiques ou de photos ;
  • OptionMenu liste déroulante ;
  • ScrolledText widget Text avec ascenseur ;
  • PanedWindow interface à onglets ;
  • LabelFrame contenant pour organiser d'autres widgets, avec un cadre et un titre ;
  • Spinbox un widget de sélection multiple.

8-2-3. Le positionnement des widgets

tkinter possède trois gestionnaires de positionnement :

  • le packer : dimensionne et place chaque widget dans un widget conteneur selon l'espace requis par chacun d'eux ;
  • le gridder : dimensionne et positionne chaque widget dans les cellules d'un tableau d'un widget conteneur ;
  • le placer : dimensionne et place chaque widget dans un widget conteneur selon l'espace explicitement demandé. C'est un placement absolu (usage peu fréquent).

8-3. Trois exemples

8-3-1. Une calculette

Cette application(28) implémente une calculette simple, mais complète (Fig. 8.3).

 
Sélectionnez
from tkinter import *
from math import *

def evaluer(event) :
    chaine.configure(text = '=> ' + str(eval(entree.get())))

# Programme principal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fenetre = Tk()
entree = Entry(fenetre)
entree.bind('<Return>', evaluer)
chaine = Label(fenetre)
entree.pack()
chaine.pack()

fenetre.mainloop()
Image non disponible
Figure 8.3 - Une calculette graphique minimale

La fonction evaluer() est exécutée chaque fois que l'utilisateur appuie sur la touche Image non disponible après avoir entré une expression mathématique. Cette fonction utilise la méthode configure() du widget chaine pour modifier son attribut text. Pour cela, evaluer() exploite les caractéristiques de la fonction intégrée eval() qui analyse et évalue son argument, une chaîne d'expression mathématique. Par exemple :

 
Sélectionnez
>>> eval('(25 + 8) / 3')
11.0

Le programme principal se compose de l'instanciation d'une fenêtre Tk() contenant un widget chaine de type Label() et un widget entree de type Entry(). Nous associons l'événement <Return> au widget entree de façon qu'un appui de la touche Image non disponible déclenche la fonction evaluer(). Enfin, après avoir positionné les deux widget à l'aide de la méthode pack(), on active la boucle d'événement mainloop().

8-3-2. tkPhone, un exemple sans menu

On se propose de créer un script de gestion d'un carnet téléphonique. L'aspect de l'application est illustré Fig. 8.4.

8-3-2-a. Notion de callback

Nous avons vu que la programmation d'interface graphique passe par une boucle principale chargée de traiter les différents événements qui se produisent.

Cette boucle est généralement gérée directement par la bibliothèque d'interface graphique utilisée, il faut donc pouvoir spécifier à cette bibliothèque quelles fonctions doivent être appelées dans quels cas. Ces fonctions sont nommées des callbacks — ou rappels —, car elles sont appelées directement par la bibliothèque d'interface graphique lorsque des événements spécifiques se produisent.

8-3-2-b. Conception graphique

La conception graphique va nous aider à choisir les bons widgets.

En premier lieu, il est prudent de commencer par une conception manuelle ! En effet rien ne vaut un papier, un crayon et une gomme pour se faire une idée de l'aspect que l'on veut obtenir.

Dans notre cas on peut concevoir trois zones :

  1. Une zone supérieure, dédiée à l'affichage ;
  2. Une zone médiane est une liste alphabétique ordonnée ;
  3. Une zone inférieure est formée de boutons de gestion de la liste ci-dessus.

Chacune de ces zones est codée par une instance de Frame(), positionnée l'une sous l'autre grâce au packer, et toutes trois incluses dans une instance de Tk() (cf. conception Fig. 8.4).

Image non disponible
Figure 8.4 - tkPhone

8-3-2-c. Le code de l'interface graphique

Méthodologie : on se propose de séparer le codage de l'interface graphique de celui des callbacks. Pour cela on propose d'utiliser l'héritage entre une classe parente chargée de gérer l'aspect graphique et une classe enfant chargée de gérer l'aspect fonctionnel de l'application contenu dans les callbacks. Comme nous l'avons vu précédemment (cf. §7.6Notion de conception orientée objet), c'est un cas de polymorphisme de dérivation.

Voici donc dans un premier temps le code de l'interface graphique. L'initialisateur crée l'attribut phoneList, une liste qu'il remplit avec le contenu du fichier contenant les données (si le fichier n'existe pas, il est créé), crée la fenêtre de base root et appelle la méthode makeWidgets().

Cette méthode suit la conception graphique exposée ci-dessus et remplit chacun des trois frames.

Les callbacks sont vides (instruction pass minimale).

Comme tout bon module, un autotest permet de vérifier le bon fonctionnement (ici le bon aspect) de l'interface :

 
Sélectionnez
# -*- coding : utf-8 -*-

# Bob Cordeau
# tkPhone_IHM.py

import tkinter as tk
from os.path import isfile

# class
class Allo_IHM :
    """IHM de l'application 'répertoire téléphonique'."""
    def __init__(self, fic) :
        """Initialisateur/lanceur de la fenêtre de base"""
        self.phoneList = []
        self.fic = fic
        if isfile(self.fic) :
            with open(self.fic) as f :
                for line in f :
                    self.phoneList.append(line[:-1].split('*'))
        else :
            with open(self.fic, "w", encoding="utf8") :
                pass

        self.phoneList.sort()
        self.root = tk.Tk()
        self.root.title("Allo !")
        self.root.config(relief=tk.RAISED, bd=3)
        self.makeWidgets()
        self.nameEnt.focus()
        self.root.mainloop()

    def makeWidgets(self) :
        """Configure et positionne les widgets"""
        # frame "saisie" (en haut avec bouton d'effacement)
        frameH = tk.Frame(self.root, relief=tk.GROOVE, bd=2)
        frameH.pack()

        tk.Label(frameH, text="Nom :").grid(row=0, column=0, sticky=tk.W)
        self.nameEnt = tk.Entry(frameH)
        self.nameEnt.grid(row=0, column=1, sticky=tk.W, padx=5, pady=10)

        tk.Label(frameH, text="Tel :").grid(row=1, column=0, sticky=tk.W)
        self.phoneEnt = tk.Entry(frameH)
        self.phoneEnt.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
        
        b = tk.Button(frameH, text="Effacer", command=self.clear)
        b.grid(row=2, column=0, columnspan=2, pady=3)
        
        # frame "liste" (au milieu)
        frameM = tk.Frame(self.root)
        frameM.pack()
        
        self.scroll = tk.Scrollbar(frameM)
        self.select = tk.Listbox(frameM, yscrollcommand=self.scroll.set, height=6)
        self.scroll.config(command=self.select.yview)
        self.scroll.pack(side=tk.RIGHT, fill=tk.Y, pady=5)
        self.select.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, pady=5)
        ## remplissage de la Listbox
        for i in self.phoneList :
            self.select.insert(tk.END, i[0])
        self.select.bind("<Double-Button-1>", lambda event : self.afficher(event))
        
        # frame "boutons" (en bas)
        frameB = tk.Frame(self.root, relief=tk.GROOVE, bd=3)
        frameB.pack(pady=3)

        b1 = tk.Button(frameB, text="Ajouter", command=self.ajouter)
        b2 = tk.Button(frameB, text="Supprimer", command=self.supprimer)
        b3 = tk.Button(frameB, text="Afficher", command=self.afficher)
        b1.pack(side=tk.LEFT, pady=2)
        b2.pack(side=tk.LEFT, pady=2)
        b3.pack(side=tk.LEFT, pady=2)

    def ajouter(self) :
        pass

    def supprimer(self) :
        pass

    def afficher(self, event=None) :
        pass

    def clear(self) :
        pass

# autotest -------------------------------------------------------------------
if __name__ == '__main__' :
    # instancie l'IHM, callbacks inactifs
    app = Allo_IHM("./phones.txt")

8-3-2-d. Le code de l'application

On va maintenant utiliser le module de la façon suivante :

  • on importe la classe Allo_IHM depuis le module précédent ;
  • on crée une classe Allo qui en dérive ;
  • son initialisateur appelle l'initialisateur de la classe de base pour hériter de toutes ses caractéristiques ;
  • il reste à surcharger les callbacks.

Enfin, le script instancie l'application :

 
Sélectionnez
# -*- coding : utf-8 -*-

# Bob Cordeau
# tkPhone.py

# import ----------------------------------------------------------------------
from tkPhone_IHM import Allo_IHM

# définition de classe --------------------------------------------------------
class Allo(Allo_IHM) :
    """Répertoire téléphonique."""
    def __init__(self, fic='phones.txt') :
        """Constructeur de l'IHM."""
        super().__init__(fic)

    def ajouter(self) :
        # maj de la liste
        ajout = ["",""]
        ajout[0] = self.nameEnt.get()
        ajout[1] = self.phoneEnt.get()
        if (ajout[0] == "") or (ajout[1] == "") :
            return
        self.phoneList.append(ajout)
        self.phoneList.sort()
        # maj de la listebox
        self.select.delete(0, 'end')
        for i in self.phoneList :
            self.select.insert('end', i[0])
        self.clear()
        self.nameEnt.focus()
        # maj du fichier
        f = open(self.fic, "a")
        f.write("%s*%s\n" % (ajout[0], ajout[1]))
        f.close()

    def supprimer(self) :
        self.clear()
        # maj de la liste
        retrait = ["",""]
        retrait[0], retrait[1] = self.phoneList[int(self.select.curselection()[0])]
        self.phoneList.remove(retrait)
        # maj de la listebox
        self.select.delete(0, 'end')
        for i in self.phoneList :
            self.select.insert('end', i[0])
        # maj du fichier
        f = open(self.fic, "w")
        for i in self.phoneList :
            f.write("%s*%s\n" % (i[0], i[1]))
        f.close()

    def afficher(self, event=None) :
        self.clear()
        name, phone = self.phoneList[int(self.select.curselection()[0])]
        self.nameEnt.insert(0, name)
        self.phoneEnt.insert(0, phone)

    def clear(self) :
        self.nameEnt.delete(0, 'end')
        self.phoneEnt.delete(0, 'end')

# programme principal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
app = Allo() # instancie l'application

8-3-3. IDLE, un exemple avec menu

Généralement les distributions Python comportent l'application IDLE, l'interpréteur/éditeur écrit en Python par Guido van Rossum(29). Cette application se présente sous l'aspect d'une interface graphique complète (Fig. 8.5), avec menu.

C'est un source Python dont le code est librement disponible(30) et constitue à lui seul un cours complet à tkinter(31).

Image non disponible
Figure 8.5 - IDLE

précédentsommairesuivant
Langage développé en 1988 par John K. Ousterhout de l'Université de Berkeley.
Adaptée de [1].
Dans certaines distributions GNU/Linux, IDLE est un package particulier.
Mais il est trop volumineux pour être reproduit dans ces notes…
Signalons IDLEX une extension à IDLE : http://idlex.sourceforge.net/.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Kordeo. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.