Interfaces graphiques
Selon Wikipedia, une interface graphique (en anglais GUI pour graphical user interface) ou environnement graphique est un dispositif de dialogue homme-machine.
Le dialogue s’effectue grâce à :
un écran qui affiche ou recueille l’information dans des fenêtres à travers des composants programmables (widgets) ;
et des dispositifs d’entrée-sortie (clavier, souris, trackpad, joystick, etc…) qui permettent à l’humain de fournir cette information à la machine.
Le développement d’interfaces graphiques peut se décomposer en 2 étapes principales :
positionner les widgets nécessaires dans la fenêtre graphique ;
puis paramétrer ces widgets pour les associer à une action spécifique lorsqu’ils sont activés (clic sur un bouton par exemple).
Ces opérations sont conduites en utilisant une bibliothèque graphique qui fournit l’environnement nécessaire à la programmation.
Il existe de nombreuses bibliothèques graphiques. Pour ce cours, nous travaillerons avec la bibliothèque Tcl/Tk interfacée avec Python via le module tkinter
.
L’interfaçage permet d’utiliser des objets Python qui se chargent de produire les commandes Tcl/Tk de plus bas niveau.
Installation
Pour Windows et MacOS, les installations officielles de Python incluent le module tkinter
et aucune autre opération n’est nécessaire.
Pour Linux/Ubuntu, pour économiser de la mémoire, tkinter
n’est pas installé par défaut. Un package additionnel doit être installé
$ sudo apt-get install python3-tk
Quel que soit le système d’exploitation, si l’installation est opérationnelle, l’import de tkinter
doit se passer sans erreur:
$ python
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import tkinter
>>>
L’exécution de la commande suivante doit provoquer l’apparition d’une fenêtre minimale permettant également de valider l’installation:
$ python -m tkinter
Une première fenêtre
Une fois tkinter opérationnel, on crée une fenêtre graphique :
en important le package ;
en instanciant la classe
tkinter.Tk
.
Dans un interpréteur interactif :
>>> import tkinter as tk
>>> window = tk.Tk()
Une fenêtre vide apparait à l’écran.
Pour le moment, une telle fenêtre ne dispose que des interactions avec le système d’exploitation (minimiser, agrandir, fermer) et n’est donc pas très utile.
Dans ce qui suit, on va ajouter des widgets permettant de la doter de capacités supplémentaires.
Les widgets
L’élément principal de l’interface graphique (GUI) est la fenêtre (window) construite à l’étape précédente. Pour ajouter des fonctionnalités à cette fenêtre, Tkinter dispose de nombreux widgets permettant de mettre en oeuvre des interactions avec l’utilisateur. La fenêtre est donc un container hébergeant l’ensemble des éléments graphiques (widgets) qui composent la GUI. Les principaux widgets sont :
Widget Class |
Description |
---|---|
Label |
Utilisé pour afficher du texte ou des images |
Button |
peut être associé à une action lorsqu’il est cliqué |
Entry |
permet à l’utilisateur d’entrer une ligne de texte |
Text |
permet à l’utilisateur d’entrer plusieurs lignes de texte |
Frame |
un container rectangulaire permettant de grouper des widgets |
Note
Les widgets de Tkinter sont parfaitement fonctionnels mais ont un look un peu daté. Les widgets tkinter.ttk
sont plus modernes mais pour des raisons de simplicité, ils ne seront pas utilisés ici. Cependant, à la fin du cours, vous disposerez des connaissances nécessaires pour les mettre en oeuvre sans difficulté.
Chaque widget dispose de nombreuses propriétés permettant de définir leur apparence graphique ou leur comportement. Nous illustrerons ça dans les paragraphes qui suivent.
Le widget Label
Un Label
est utilisé pour afficher du texte et des images. L’ajout d’un Label
se fait en deux temps :
on crée une instance de la classe ;
et on l’ajoute à la fenêtre graphique avec un gestionnaire graphique. Ici on utilise la méthode pack() (on verra d’autres gestionnaires graphiques ultérieurement).
Ceci se fait avec les deux instructions suivantes
>>> label = tk.Label(text="Label")
>>> label.pack()
Note
La construction d’un widget est une condition nécessaire mais pas suffisante pour le faire apparaître à l’écran. Il faut que celui ci soit positionné avec un gestionnaire graphique.
Un des problèmes importants de la création de GUI est le dimensionnement des window et des widget. Ici un dimensionnement par défaut a été utilisé, consistant à adapter la taille de la fenêtre au widget qu’elle contient.
Le paradigme événementiel
La construction de GUI utilise le paradigme événementiel qui scrute indéfiniment les évènements liés à la fenêtre (clic sur un bouton, appui sur une touche du clavier, fermeture de la fenêtre, etc.) pour déclencher l’action liée à cet évènement.
Important
Le paradigme événementiel est très différent du paradigme impératif ou objet utilisés jusque là.
La boucle de scrutation est interne à tkinter
. Ceci dispense l’utilisateur d’écrire sa propre boucle while
:
dans un interpréteur interactif, la scrutation est déclenchée automatiquement. C’est cette scrutation qui fait que l’on peut fermer la fenêtre avec l’icone en haut à droite (interaction avec le système d’exploitation).
si le code est stocké dans un fichier, on rentre dans la scrutation des évènements en appelant explicitement la méthode
mainloop()
. Si cette méthode n’est pas appelée, la fenêtre n’apparait pas à l’écran.
Note
Pour le moment, la seule façon de mettre fin à la boucle de scrutation est de fermer la fenêtre .
Le code complet est stocké dans le fichier ci dessous.
1# first_window.py
2
3import tkinter as tk
4
5window = tk.Tk()
6
7label = tk.Label(text="Hello world !")
8label.pack()
9
10window.mainloop()
On dispose maintenant d’une structure minimale qui :
crée une fenêtre principale (ligne 5);
lui ajoute un widget (lignes 7 et 8);
et démarre la scrutation d’évènements (ligne 10).
Paramétrer un Label
Un Label <https://tcl.tk/man/tcl8.6/TkCmd/label.html> dispose de nombreuses propriétés.
Note
La documentation décrit l’ensemble des propriétés du widget mais les exemples utilisent le langage de script Tcl. La transposition en Python est immédiate.
Tcl :
label .t -text "Hello World !" -bg red
;Python :
Label(text="Hello world !", bg="red")
.
Certaines propriétés sont communes à plusieurs widgets :
anchor
: position du texte [n/ne/e/se/s/sw/w/nw/center]background
orbg
: couleur de fondborderwidth
orbd
: largeur de la bordurefont
: police de caractèresforeground
orfg
: couleur du texteimage
: affichage d’une imagepadx
: espace additionnel en Xpady
: espace additionnel en Ytext
: texte à afficher
D’autres sont spécifiques au tk.Label
:
height
: hauteurstate
: état (normal/active/disabled)width
: largeur
Note
Les couleurs <https://tcl.tk/man/tcl8.6/TkCmd/colors.html>`_ sont définies par leur nom ou par leur code hexadecimal RGB:
hello = tk.Label(text="Hello world !", bg="aquamarine4")
hello = tk.Label(text="Hello world !", bg="#458B74")
Pour le choix des couleurs, on peut travailler avec des palettes prédéterminées, ou utiliser une Color Wheel pour plus de flexibilité.
Astuce
Les propriétés width
et height
sont spécifiées en unités de texte. La largeur et la hauteur du caractère 0
sont utilisées comme références. Comme ce caractère est plus haut que large, définir une largeur et une hauteur identique ne conduit pas à un widget carré !
Le widget Entry
Un Entry <https://tcl.tk/man/tcl8.6/TkCmd/entry.html> est un widget permettant à l’utilisateur d’entrer un texte court. Quelques propriétés (communes à d’autres widgets):
background
orbg
: couleur de fond ;borderwidth
orbd
: largeur de la bordure ;font
: police de caractères ;foreground
orfg
: couleur du texte.
Quelques propriétés spécifiques :
state
: état (normal/active/disabled)width
: largeur
Le widget Entry
possède également des méthodes permettant d’interagir avec le contenu :
la méthode
get()
permet de récupérer le texte entré par l’utilisateur ;la méthode
delete()
permet de supprimer le texte ;et la méthode
insert()
permet d’insérer du texte.
Le widget Frame
Une Frame <https://tcl.tk/man/tcl8.6/TkCmd/frame.html> est un container contenant d’autres widgets. Son rôle est de structurer (et de simplifier) l’affichage en manipulant un ensemble de widgets à travers la Frame
dans laquelle ils sont placés. La fenêtre principale de l’application est également un container et se comporte donc comme une Frame
.
Une Frame
possède un gestionnaire graphique, dont le rôle est de positionner les widgets selon un algorithme qui dépend de la méthode employée. La méthode pack()
utilisée précédemment est un type de gestionnaire graphique.
Quelques propriétés d’une Frame
:
background
orbg
: couleur de fond ;borderwidth
orbd
: largeur de la bordure ;cursor
: le pointeur de souris utilisé lorsque la souris est à l’intérieur de laFrame
;height
: la hauteur de laFrame
;padx
: ajoute de l’espace supplémentaire horizontalement ;pady
: ajoute de l’espace supplémentaire verticalement ;width
: la largeur de laFrame
;
Les gestionnaires graphiques
La disposition des widgets dans une Frame
(ou dans la fenêtre principale s’il n’y a pas de Frame
) peut se faire suivant 3 méthodes:
pack()
;grid()
;place()
;
La méthode pack()
La méthode pack()
permet de positionner les widgets les uns par rapport aux autres et est très efficace lorsqu’on ne souhaite pas un contrôle strict sur le layout de l’application.
Considérons le code ci dessous qui construit une fenêtre avec deux labels.
1# 13-pack.py
2
3import tkinter as tk
4
5window = tk.Tk()
6window.title('La méthode pack()')
7window.geometry("350x200")
8
9label1 = tk.Label(window, text="Label 1", bg="green", fg="white")
10label2 = tk.Label(window, text="Label 2", bg="red", fg="white")
11
12label1.pack()
13label2.pack()
14
15window.mainloop()
Le gestionnaire pack()
place les labels dans l’ordre où il les rencontre. Il suffit d’intervertir les lignes 12 et 13 pour s’en convaincre.
On peut passer des arguments à la méthode pack()
, par exemple pour insérer un espacement à l’intérieur ou à l’extérieur des widgets.
1# 13-pack.py
2
3import tkinter as tk
4
5window = tk.Tk()
6window.title('La méthode pack()')
7window.geometry("350x200")
8
9label1 = tk.Label(window, text="Label 1", bg="green", fg="white")
10label2 = tk.Label(window, text="Label 2", bg="red", fg="white")
11
12label1.pack(ipadx=10, ipady=10)
13label2.pack(padx=10, pady=10)
14
15window.mainloop()
Les principales propriétés de la méthode pack()
:
padx
: ajoute de l’espace supplémentaire horizontalement (à l’extérieur du widget) ;pady
: ajoute de l’espace supplémentaire verticalement (à l’extérieur du widget) ;ipadx
: ajoute de l’espace supplémentaire horizontalement (à l’intérieur du widget) ;ipady
: ajoute de l’espace supplémentaire verticalement (à l’intérieur du widget) ;fill
option de remplissage de l’espace alloué au widget. 3 valeurs possibles :tkinter.X
: remplissage selon l’axe des x ;tkinter.Y
: remplissage selon l’axe des y ;tkinter.BOTH
: remplissage selon les deux axes.
expand
: alloue de l’espace supplémentaire au widget ;anchor
positionne le widget dans l’espace alloué. Plusieurs valeurs possibles :N
: en haut ;S
: en bas ;E
: à droite ;W
: à gauche ;NW
: en haut à gauche ;NE
: en haut à droite ;SE
: en bas à droite ;SW
: en bas à gauche ;CENTER
: au centre.
side
positionne l’espace alloué au widget dans laFrame
. 4 valeurs possibles :tkinter.TOP
;tkinter.LEFT
;tkinter.RIGHT
;tkinter.BOTTOM
.
Exercice
Combiner les propriétés de la méthode pack()
pour obtenir les figures ci dessous :
Une première application
Application
Développer une application permettant d’afficher le nombre de clics effectué sur un bouton. Ajouter une fonctionnalité permettant de mesurer l’intervalle de temps entre deux clics.
Application
Développer une application calculette permettant d’afficher le résultat des 4 opérations élémentaires.