Un module pour interface graphique (GUI pour Graphical User Interface en anglais) est un module qui fournit des fonctions et des classes permettant de créer des fenêtres par lesquelles l'utilisateur pourra interagir dynamiquement avec l'ordinateur. Nous allons utiliser le module Tkinter fourni avec Python. Nous l'importons ici sous le nom raccourci tk:
import tkinter as tk
Attention: durant des essais répétés sous IPython, il se peut que des messages d'erreurs se présentent alors que tout devrait bien se passer. Il peut être alors utile de redémarrer le noyau (Kernel) par la commande $\circlearrowright$.
Nous allons voir comment cela fonctionne sur des exemples.
Premier exemple, on ouvre une fenêtre qui affiche un texte (regarder dans la barre des tâches si la fenêtre ne s'ouvre pas directement).
fenetre = tk.Tk()
widget = tk.Label(fenetre, text="Hello, world!")
widget.pack()
fenetre.mainloop()
Fermer la fenêtre comme habituellement. Les fenêtres, boutons, etc... sont appelés widgets en anglais (traduction difficile: gadget, truc, bidule, chose,...). La première ligne crée un widget générique de type Tk et qui va correspondre à la fenêtre principale. Ici une fenêtre vide si rien de plus n'est précisé (vous pouvez commenter les deux lignes du milieu pour voir l'effet). On affecte l'instance créée à la variable fenetre. La seconde ligne crée un widget de type Label (étiquette en anglais) qui est une classe fournie par TKinter, en la liant à la variable fenetre. On lui passe en argument un texte à afficher. Ensuite, on appelle la méthode pack du widget qui fait en sorte que le texte soit affiché dans une fenêtre aux dimensions adaptées. Finalement, le véritablement lancement de la fenêtre se fait avec la méthode mainloop. Elle lance l'interface graphique en créant la fenêtre et attend une réponse de l'utilisateur. Ici, la fermeture de la fenêtre met fin à mainloop.
Deuxième exemple. On crée un widget simple avec une classe appelée Application et qui va utiliser le type Button fourni par **TKinter.
On peut comprendre le fonctionnement de Button en regardant le cas simplifié suivant:
fenetre = tk.Tk()
bouton = tk.Button(fenetre, text="Quitter", command=fenetre.destroy)
bouton.pack()
fenetre.mainloop()
L'objet Button prend comme arguments un widget, un texte à afficher et enfin une fonction passée à la variable command. Cette dernière est particulièrement importante: elle va indiquer à l'interface graphique ce qu'il faut faire si l'utilisateur appuie sur le bouton. Dans cet exemple, on lui passe la méthode destroy de la fenetre qui va détruire celle-ci. Ainsi, lorsqu'on appuie sur le bouton, la fenêtre est détruire. C'est bien le comportement attendu pour un bouton Quitter.
On crée une mini-application en définissant la classe suivante:
import tkinter as tk
class Application:
def __init__(self, widget):
self.ref = widget
cadre = tk.Frame(widget)
cadre.pack()
self.bouton = ???? # à compléter
???? # à compléter
def afficher(self):
print("Bien, merci.")
???? # a compléter
fenetre = tk.Tk()
app = Application(fenetre)
fenetre.mainloop()
Cet exemple est construit comme suit: on crée une classe Application dont on crée une instance en lui passant en argument un autre widget, typiquement, le widget générique Tk correspondant à une fenêtre principale. Ensuite, le constructeur init crée un cadre de type Frame et on utilise la méthode pack pour le rendre visible.
Questions
On veut créer une calculatrice dans laquelle on entre une expression mathématique à calculer, écrite en langage Python, et dont on veut faire afficher le résultat dans la fenêtre, juste en-dessous.
Pour ce faire, étudions le fonctionnement du widget Entry fourni par TKinter et qui permet de récupérer un texte entré par l'utilisateur. Ce widget permet à l'utilisateur de rentrer un texte qui est ensuite récupéré par la méthode get de Entry. Ci-dessous, ce qui est récupéré de l'objet entree de type Entry est passé dans l'option text d'un Label pour pouvoir être affiché. La récupération de la donnée se fait via un bouton qui appel la fonction repondre.
def repondre():
affichage['text'] = entree.get()
fenetre = tk.Tk()
entree = tk.Entry(fenetre)
action = tk.Button(fenetre, text ='Faire afficher le texte', command=repondre)
affichage = tk.Label(fenetre, width=30)
entree.pack()
action.pack()
affichage.pack()
fenetre.mainloop()
Nous avons maintenant quelques mécanismes de base pour créer une mini-calculette. Une proposition à compléter est faite ci-dessous. On notera que la fonction pack n'est pas utilisée au profit de grid qui positionne les widgets créés sur une grille dont on précise le numéro de la colonne (column) et de la ligne (row). On notera également que en dehors des widgets Entry et Label qui reservent dans d'autres méthodes, on n'a pas besoin de stocker dans des variables tous les widgets créés dans init. Enfin, ce widget est créé sous forme d'une classe pour montrer qu'il sera facilement réutilisable en quelques lignes, évitant de devoir taper une série longue d'instruction.
import tkinter as tk
from math import *
class Calculette:
def __init__(self, parent):
self.ref = parent
parent.grid()
tk.Label(parent, text="Expression").grid(row=0,column=0)
self.entree = tk.Entry(parent, width=30)
self.entree.grid(row=0,column=1)
tk.Button(parent, text="Calculer", command=????).grid(row=0,column=2) # à compléter
tk.Label(self.ref, text='Le résultat est :').grid(row=1,column=0)
self.affichage = tk.Label(self.ref, width=30)
tk.Button(parent, text="Remettre à zéro", command=????).grid(row=2,column=1) # à compléter
tk.Button(parent, text="Quitter", command=????).grid(row=2,column=2) # à compléter
def reset(self):
self.entree.delete(0,tk.END)
self.affichage['text'] = ''
def calculer(self):
self.affichage['text'] = ???? # à compléter
self.affichage.grid(row=1,column=1)
fenetre = tk.Tk()
fenetre.title("Calculatrice Python")
cal = Calculette(fenetre)
fenetre.mainloop()
Questions
On veut pouvoir utiliser la touche Return ou Entree du clavier pour lancer le calcul plutôt qu'utiliser le clic sur un bouton. Pour cela, on peut lier (bind en anglais) l'appui d'une touche clavier au déclenchement d'une fonction. La syntaxe est décrite ci-dessous: la variable entree de type Entry est liée par la touche "Return" à la fonction calculer. Le déclenchement de "Return" dans le champ de entree entraînera donc l'appel de la fonction calculer. Cette fonction prend comme argument event (un évènement) qui est géré par la librairie TKinter.
import tkinter as tk
from math import *
def calculer(event):
expression['text'] = "Le résultat est "+???? # à compléter
fenetre = tk.Tk()
entree = tk.Entry(fenetre)
entree.bind("<Return>", calculer)
expression = tk.Label(fenetre)
entree.pack()
expression.pack()
fenetre.mainloop()
Question
Le widget Scale permet de créer un curseur dont la valeur détectée en cas de mouvement du curseur est transmise en argument de la fonction qui est donnée à l'option command dans la définition du curseur. Voici un exemple.
import tkinter as tk
def afficher(valeur):
print("Votre prochaine note sera",valeur+"/10")
fenetre = tk.Tk()
curseur = tk.Scale(fenetre, from_=0, to=10, resolution=0.5, length=120, orient="horizontal",\
command=afficher, label = "Note de l'étudiant")
curseur.pack()
curseur.set(5.0)
fenetre.mainloop()
Questions
On peut créer ou importer une image dans un dessin (classe Canvas) à l'aide de la classe PhotoImage. Les unités de dimensions sont en pixels pour l'affichage à l'écran. Les lignes
"#%02x%02x%02x" % (R,G,B)
assure la conversion de valeurs entre 0 et 255 en hexadecimaux codant pour une couleur RGB.
import tkinter as tk
fenetre = tk.Tk()
dessin = tk.Canvas(fenetre)
dessin.pack()
img = tk.PhotoImage(width=100, height=100)
dessin.create_image((3,3),image=img, anchor="nw", state="normal")
R,G,B = 200, 156, 101
img.put("#%02x%02x%02x" % (R,G,B), to=(50,50,70,70))
img.put("#%02x%02x%02x" % (0,0,255), to=(0,0,10,10))
img.put("#%02x%02x%02x" % (0,255,0), to=(90,90,10,100))
fenetre.mainloop()
Question
On associe les différents mécanismes vus ci-dessus pour créer une image 2D d'une (presque, la formule n'est pas tout à fait correcte physiquement) figure de diffraction. Lire attentivement le code ci-dessus puis répondre aux questions avant de pouvoir le lancer et voir le résultat:
import tkinter as tk
import numpy as np
# paramètres
Lx, Ly, taille_pixel = 60, 60, 8
largeur, hauteur = Lx*taille_pixel, Ly*taille_pixel
i0, j0 = Lx/2., Ly/2.
# création de la fenêtre principale
fenetre = tk.Tk()
fenetre.title("Figure de diffraction")
fenetre.geometry('+50+50')
# création du dessin à l'intérieur
dessin = tk.Canvas(fenetre, width=largeur, height=hauteur)
dessin.pack()
img = tk.PhotoImage(width=largeur, height=hauteur)
dessin.create_image((3, 3), image=img, anchor="nw", state="normal")
# fonctions de mise à jour de l'image
sinc = lambda x: (np.sin(x)/x)**2 if abs(x) > 1e-15 else 1.0
def majFreq(f): afficher(freq=float(f),cutoff=curseur_c.get())
def majCutoff(c): afficher(freq=curseur_f.get(),cutoff=float(c))
def afficher(freq=1.0,cutoff=0.5):
s = np.ones((Lx, Ly), float)
for i in range(Lx):
for j in range(Ly):
radius = np.sqrt((i-i0)**2+(j-j0)**2)/float(Lx)
s[i,j] = min(sinc(2*np.pi*freq*radius),cutoff)
s /= np.max(s)
for i in range(Lx):
for j in range(Ly):
gl = 255*s[i,j]
img.put("#%02x%02x%02x" % (int(gl),int(gl),int(0.5*gl)), \
to=(i*taille_pixel,j*taille_pixel,(i+1)*taille_pixel,(j+1)*taille_pixel))
# création d'une sous-fenêtre pour les curseurs
fenetre_controle = tk.Frame(fenetre)
fenetre_controle.pack()
#insertion des curseurs, à compléter
curseur_f = ????
curseur_f.pack(side="left")
????
curseur_c = ????
curseur_c.pack(side="left")
????
# lancement de l'application
fenetre.mainloop()
Questions