Ressources⚓︎
Tutoriels Tkinter
Deux tutoriels de présentation du module d'interface graphique tkinter :
Sitographie de sites resssources sur tkinter :
Introduction⚓︎
Exercice 1
-
Télécharger le script hello_world.py et l'exécuter dans l'IDE Spyder.
code source
🐍 Script Pythonfrom tkinter import * # import du module fen = Tk() # création de la fenêtre racine msg = Label(fen, text="Hello World") # création d'un widget Label dans la fenêtre racine msg.pack() # positionnement géométrique du widget avec la méthode `pack` fen.mainloop() # boucle infinie avec réceptionnaire d'événements
À retenir
- tkinter est un module de la bibliothèque standard qui permet de réaliser des interfaces graphiques.
- Chaque objet graphique, appelé widget, est créé avec un constructeur, par exemple Label dont le premier argument est l'objet parent dans lequel il est placé.
- Le widget créé avec le constructeur
Tk
est l'ancêtre de tous les autres, il n'a pas de parent, c'est la fenêtre racine. De plus il n'est pas nécessaire de positionner géométriquement cet objet. - Pour que le widget construit s'affiche il faut deux instructions supplémentaires :
- le positionnement géométrique du widget dans son parent : dans l'exemple ci-dessus avec la méthode
pack
. On utilise la notation pointéeobjet.methode()
- en dernière instruction du programme, il faut lancer la méthode
mainloop
sur la fenêtre racine : c'est une boucle infinie avec un réceptionnaire d'événements qui va capturer tous les événements (appui sur touche, mouvement de la souris ...). En effet une interface graphique fonctionne avec une programmation par événements.
- le positionnement géométrique du widget dans son parent : dans l'exemple ci-dessus avec la méthode
-
Remplacer
msg = Label(fen, text="Hello World")
parmsg = Label(fen, text="Hello World", font=("Arial", 20))
. Exécuter, observer. -
La fenêtre est-elle redimensionnable ? Insérer les instructions suivantes, juste après la définition de la fenêtre racine. Exécuter, observer.
🐍 Script Pythonfen.geometry("300x50") fen.resizable(width=False, height=False)
Exercice 2
-
Télécharger le script controle_barre.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * # import du module tkinter ### Constantes (valeurs non modifiables à l'exécution) LARG_BARRE = 60 # largeur de la barre en pixels HAUT_BARRE = 20 # hauteur de la barre en pixels DX = 10 # déplacement horizontal en pixels pour un appui sur touche DY = 10 # déplacement vertical en pixels pour un appui sur touche HAUT_FENETRE = 400 LARG_FENETRE = 400 ### Variables globales (valeurs modifiables à l'exécution) x_barre = 160 # abscisse de la barre y_barre = 180 # ordonnée de la barre ### Modèle def deplacement(event): """Fonction gestionnaire de l'événement appui sur une touche""" global x_barre, y_barre # variables globales if event.keysym == 'Up': y_barre = y_barre - DY elif event.keysym == 'Down': y_barre = y_barre + DY # à compléter avec les déplacements droite / gauche # on modifie les coordonnées de la barre dans le widget canevas can.coords(barre, x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE) # on met à jour l'affichage de la fenêtre fen.update() ### Vue : interface graphique # Fenêtre racine fen = Tk() fen.title("Controle Barre") # Canevas d'affichage de la barre can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE) can.pack() # Barre rectangulaire dessinée dans le canevas barre = can.create_rectangle(x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE, fill="white") ### Controleur # Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement fen.bind("<KeyPress>", deplacement) ### Boucle infinie , réceptionnaire d'événement fen.mainloop()
À retenir
Dans cet exemple, on organise le programme selon l'architecture Modèle - Vue - Contrôleur :
Source : Wikipedia
Partie Vue
C'est la mise en place de tous les widgets de l'interface graphique. Ici la fenêtre racine contient un widget Canvas qui est une zone de dessin. Dans ce canevas, on dessine un rectangle. Ce n'est pas un widget, c'est un item du canevas : la méthode de positionnement géométrique
pack
s'applique uniquement au canevas pour le positonner dans son parent la fenêtre racine.Partie Modèle
La fonction
deplacement
va être appelée par un contrôleur pour mettre à jour un objet graphique, ici l'item barre dessiné dans le canevas. Elle prend en paramètre l'événementevent
qui lui a été transmis et selon sa valeur (le symbole de la touche appuyée) modifie l'abscisse ou l'ordonnée avec la méthodecoords
du canevas. Après avoir modifié l'objet graphique, il faut mettre à jour l'affichage de toute la fenêtre racine avecfen.update()
.deplacement
est un gestionnaire d'événement.Repérage dans le canevas
Traditionnellement en informatique, l'origine du repère est en haut à gauche de la zone de dessin, l'axe des abscisses est orienté vers la droite et la 'axe des ordonnées vers le bas.
Partie Contrôleur
Le réceptionnaire d'événements de la boucle
mainloop()
capturetous les événements (appui touche, mouvement de la souris). L'instructionfen.bind("<KeyPress>", deplacement)
met en place un contrôleur qui va appeler l'exécution de la fonctiondeplacement
(partie Modèle) si l'événement Appui sur une touche est capturé. -
Pourquoi faut-il retrancher
DY
si on veut déplacer verticalement la barre vers le haut ? - Dans le programme les noms des constantes sont en majuscules et les noms des variables en minuscules. Quelle est la différence entre constantes et variables ?
- Dans le programme on utilise deux variables globales
x_barre
ety_barre
. Quelle est la différence avec la variable localevar_locale
? A quoi sert le mot clefglobal
dans une fonction ? - Modifier la fonction
deplacement
pour qu'elle gère aussi les déplacements horizontaux, lorsqu'on appuie sur la flèche correspondante du pavé directionnel. -
Modifier la fonction
deplacement
pour que la barre soit bloquée lorsqu'un de ses bords touche un bord du canevas. La barre ne doit pas pouvoir sortir du canevas. On pourra utiliser les fonctionsmin
etmax
. Par exemple pour que la barre ne puisse pas sortir par le bas du canevas, on peut écrire :🐍 Script Pythony_barre = max(0, y_barre - DY)
Exercice 3
-
Télécharger le script clic_ballon.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * import random ### Constantes (valeurs non modifiables à l'execution) HAUT_FENETRE = 400 LARG_FENETRE = 400 ### Fonctions : boite à outils def from_rgb(r, g, b): """ Description : Renvoie le codage RGB d'une couleur en notation HTML au format #FF00AA à partir des composantes rouge r, vert v et bleue b Paramètre : r : entier entre 0 et 255 g : entier entre 0 et 255 b : entier entre 0 et 255 Renvoie : une chaien de caractères de type str """ # Préconditions sur les paramètres assert isinstance(r, int) and 0 <= r <= 255, "r doit être un entier entre 0 et 255" assert isinstance(g, int) and 0 <= g <= 255, "g doit être un entier entre 0 et 255" assert isinstance(b, int) and 0 <= g <= 255, "b doit être un entier entre 0 et 255" # valeur renvoyée return f'#{r:02x}{g:02x}{b:02x}' ### Modèle def ballon(event): """Fonction gestionnaire de l'événement clic sur bouton gauche de souris""" rouge = 255 vert = 0 bleu = 0 can.create_oval(event.x - 10, event.y - 10, event.x + 10, event.y + 10, fill=from_rgb(rouge, vert, bleu)) fen.update() ### Vue : interface graphique # Fenetre racine fen = Tk() fen.title("Clic ballon") # Canevas d'affichage de la barre can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE) can.pack() ### Controleur # Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement fen.bind("<ButtonPress-1>", ballon) ### Boucle infinie , réceptionnaire d'événement fen.mainloop()
-
Quel est le type de la valeur renvoyée par la fonction
from_rgb
? - Insérer
print(help(from_rgb))
avantfen.mainloop()
. Que s'affiche-t-il dans la console interactive ? - Dans la fonction
from_rgb
a quoi servent les instructionsassert
? - Dans la partie Contrôleur, ajouter une instruction pour mettre en place un contrôleur qui appelle la fonction
ballon
si l'événement"<ButtonPress-1>"
(clic sur le bouton gauche de la souris) est capturé. Exécuter de nouveau et décrire le fonctionnement de ce programme. - Qu'est-ce que le codage (Rouge, Vert, Bleu) d'une couleur ?
- Dans la console interactive afficher l'aide de la fonction
randint
du modulerandom
(importé au début du programme) avechelp(random.randint)
. Modifier alors la fonctionballon
pour que lorsqu'on clique, un ballon de couleur aléatoire soit créé. tkinter utilise-t-il la notation décimale pour représenter les valeurs des composantes (Rouge, Vert, Bleu) d'un pixel ?
Exercice 4
-
Télécharger le script mention.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * ### Modèle def calcul_mention(): """Calcul de la mention Modifie la variable de contrôle mention""" m = float(moyenne.get()) if 0<= m <=20: if m < 8: mention.set("Refusé") elif m < 10: mention.set("Passe second groupe") elif m < 12: mention.set("Passable") elif m < 14: mention.set("Mention Assez Bien") # à compléter avec tous les autres cas de mention else: mention.set("Valeur non conforme") ### Vue : interface graphique # Fenêtre racine fen = Tk() fen.title("Compte à rebours") fen.geometry("600x300") fen.resizable(width=False, height=False) # Variables de contrôles (variables globales) moyenne = StringVar() # variable de contrôle du widget saisie_moyenne mention = StringVar() # variable de contrôle du widget etiq_mention # Etiquette du champ de saisie etiq_moy = Label(fen, text="Moyenne : ", font=("Arial", 20)) etiq_moy.pack() # Champ de saisie de la moyenne saisie_moyenne = Entry(fen, textvariable=moyenne, font=("Arial", 20), fg='blue') saisie_moyenne.pack() # Bouton de commande bouton = Button(fen, text="Calcul mention", command=calcul_mention, font=("Arial", 20)) bouton.pack() # Etiquette d'affichage de la mention etiq_mention = Label(fen, textvariable=mention, font=("Arial", 20), fg='red') etiq_mention.pack() ### Boucle infinie , réceptionnaire d'événement fen.mainloop()
À retenir
Dans le programme clic_ballon.py on utilise deux nouveaux types de widgets :
- Entry qui est un champe de saisie permettant de récupérer une valeur saisie par l'utilisateur.
- Button qui permet de lancer l'exécution d'une fonction lorsqu'on clique dessus. En fait il s'agit d'un contrôleur visible destiné à capturer le clic sur un emplacement précis de la fenêtre (le bouton en l'occurrence). C'est pourquoi on a un paramètre
command
qui prend pour valeur la fonction appelée lorsqu'on clique sur le bouton, c'est le même principe que pour une fonction appelée par un contrôleur d'événement comme dansfen.bind("<ButtonPress-1>", ballon)
.
Où est stockée la valeur de la saisie de l'utilisateur dans le widget Entry ?
-
Dans la variable de contrôle qui lui est passée en valeur du paramètre
textvariable
. Ici on a :🐍 Script Pythonsaisie_moyenne = Entry(fen, textvariable=moyenne, font=("Arial", 20), fg='blue')
La variable de contrôle est donc
moyenne
. Elle doit avoir été définie avant par :🐍 Script Pythonmoyenne = StringVar()
Notez qu'il s'agit d'un type de variable spécifique à tkinter. Les variables de contrôles sont des variables globales qui permettent de contrôler des valeurs liées à des widgets : par exemple la valeur du champ de saisie pour un widget Entry ou d'une étiquette pour un widget Label.
Lecture / écriture dans une variable de contrôle
La syntaxe est spécifique pour une variable de contrôle tkinter :
🐍 Script Pythonmoyenne = StringVar() # définition de la variable de contôle de type chaîne de caractère valeur = moyenne.get() # lecture moyenne.set("nouvelle valeur")
On peut modifier une variable globale de contrôle dans une fonction sans utiliser le mot clef
global
. -
On a une autre variable de contrôle
mention
qui contrôle la valeur affichée dans le widget Label. Dans la fonctioncalcul_mention
et la variablemoyenne
est lue et la variablemention
est modifiée.
-
Modifier la fonction
calcul_mention()
pour qu'elle traite tous les cas possibles de mention au bac.Mentions du bac
Source : https://www.education.gouv.fr/reussir-au-lycee/le-baccalaureat-general-10457
Les mentions ne sont attribuées qu'aux candidats obtenant le baccalauréat à l'issue du premier groupe d'épreuves, en fonction de la moyenne obtenue :
- mention assez bien (AB) : pour une moyenne entre 12 et 14 ;
- mention bien (B) : pour une moyenne entre 14 et 16 ;
- mention très bien (TB) : pour une moyenne entre 16 et 18.
Depuis la session 2021, la mention très bien avec les félicitations du jury peut être accordée aux candidats présentant une moyenne supérieure à 18.
Correction des exercices 1 à 4
Il n'est pas possible d'exécuter le module tkinter dans Capytale. Pour exécuter tkinter dans un navigateur, vous pouvez utiliser la plateforme replit.com, il existe une version gratuite mais il faut se créer un compte. Sinon il faut installer une distribution Python sur son PC et dans ce cas Edupyter est un très bon choix car c'est une distribution portable que pouvez exécuter depuis une clef USB sur n'importe quel poste sous Windows.
Code source hello_world.py
from tkinter import *
fen = Tk()
msg = Label(fen, text="Hello World")
msg.pack()
fen.mainloop()
Code source controle_barre.py
from tkinter import *
### Constantes (valeurs non modifiables à l'exécution)
LARG_BARRE = 60 # largeur de la barre en pixels
HAUT_BARRE = 20 # hauteur de la barre en pixels
DX = 10 # déplacement horizontal en pixels pour un appui sur touche
DY = 10 # déplacement vertical en pixels pour un appui sur touche
HAUT_FENETRE = 400
LARG_FENETRE = 400
### Variables globales (valeurs modifiables à l'exécution)
x_barre = 160
y_barre = 180
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Controle Barre")
# Canevas d'affichage de la barre
can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE)
can.pack()
# Barre rectangulaire dessinée dans le canevas
barre = can.create_rectangle(x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE, fill="white")
### Modèle
def deplacement(event):
"""Fonction gestionnaire de l'événement
appui sur une touche"""
global x_barre, y_barre
if event.keysym == 'Up':
y_barre = max(0, y_barre - DY)
elif event.keysym == 'Down':
y_barre = min(y_barre + DY, HAUT_FENETRE - HAUT_BARRE)
elif event.keysym == 'Right':
x_barre = min(x_barre + DX, LARG_FENETRE - LARG_BARRE,)
elif event.keysym == 'Left':
x_barre = max(0, x_barre - DX)
can.coords(barre, x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE)
fen.update()
### Controleur
# Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement
fen.bind("<KeyPress>", deplacement)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Code source clic_ballon.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
import random
### Constantes (valeurs non modifiables à l'exécution)
HAUT_FENETRE = 400
LARG_FENETRE = 400
### Fonctions :boite à outils
def from_rgb(r, g, b):
"""
Description :
Renvoie le codage RGB d'une couleur en notation HTML au format #FF00AA
à partir des composantes rouge r, vert v et bleue b
Paramètre :
r : entier entre 0 et 255
g : entier entre 0 et 255
b : entier entre 0 et 255
Renvoie :
une chaien de caractères de type str
"""
# Préconditions sur les paramètres
assert isinstance(r, int) and 0 <= r <= 255, "r doit être un entier entre 0 et 255"
assert isinstance(g, int) and 0 <= g <= 255, "g doit être un entier entre 0 et 255"
assert isinstance(b, int) and 0 <= g <= 255, "b doit être un entier entre 0 et 255"
# valeur renvoyée
return f'#{r:02x}{g:02x}{b:02x}'
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Clic ballon")
# Canevas d'affichage de la barre
can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE)
can.pack()
### Modèle
def ballon(event):
"""Fonction gestionnaire de l'événement
clic sur bouton gauche de souris"""
rouge = random.randint(0, 255)
vert = random.randint(0, 255)
bleu = random.randint(0, 255)
can.create_oval(event.x - 10, event.y - 10, event.x + 10, event.y + 10, fill=from_rgb(rouge, vert, bleu))
fen.update()
### Controleur
# Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement
fen.bind("<ButtonPress-1>", ballon)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Code source mention.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
### Modèle
def calcul_mention():
m = float(moyenne.get())
if 0 <= m <= 20:
if m < 8:
mention.set("Refusé")
elif m < 10:
mention.set("Passe second groupe")
elif m < 12:
mention.set("Passable")
elif m < 14:
mention.set("Mention Assez Bien")
elif m < 16:
mention.set("Mention Bien")
elif m < 18:
mention.set("Mention Très Bien")
else:
mention.set("Mention Très Bien avec félicitations du jury")
else:
mention.set("Valeur non conforme")
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Mention du bac")
fen.geometry("600x300")
# Variables de contrôle (variables globales)
moyenne = StringVar() # variable de contrôle du widget saisie_moyenne
mention = StringVar() # variable de contrôle du widget etiq_mention
# Etiquette du champ de saisie
etiq_moy = Label(fen, text="Moyenne : ", font=("Arial", 20))
etiq_moy.pack()
# Champ de saisie de la moyenne
saisie_moyenne = Entry(
fen, textvariable=moyenne, font=("Arial", 20), fg="blue"
)
saisie_moyenne.pack()
# Bouton de commande
bouton = Button(
fen, text="Calcul mention", command=calcul_mention, font=("Arial", 20)
)
bouton.pack()
# Etiquette d'affichage de la mention
etiq_mention = Label(fen, textvariable=mention, font=("Arial", 20), fg="red")
etiq_mention.pack()
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projets⚓︎
Exemple de gestion d'une grille
Le code ci-dessous permet de dessiner une grille de \(20 \times 30\) carrés de côté 20 pixels (fonds blanc et bord rouge) et de colorier en bleu un carré choisi aléatoirement chaque fois qu'on appuie sur le bouton.
Les carrés sont dessinés avec la méthode create_rectangle
du canevas et sont stockés dans une liste de listes grille
:
id_rec = grille[y][x]
permet de récupérer l'identifiant du carré en ligney
et colonnex
can.itemcget(id_rec, "fill")
permet de lire la couleur de ce carrécan.itemconfig(id_rec, fill="#0000ff")
permet de changer en bleu la couleur de fonds du carré (on utilise le formar hexadécimal du codage RGB)
Résultat attendu
from tkinter import *
import random
def action_bouton():
y = random.randint(0, 19)
x = random.randint(0, 29)
id_rec = grille[y][x]
print(can.itemcget(id_rec, "fill"))
can.itemconfig(id_rec, fill="#0000ff")
# Définition de l'interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Crible aléatoire")
# Canevas
can = Canvas(fen, width=300, height=200)
can.pack() # positionnement
# dessin d'une grillee de 20 x 30 rectangles de coté 10 pixels stockés dans une liste de listes
# pour récupérer leurs identifiants
grille = [
[
can.create_rectangle(
x * 10,
y * 10,
(x + 1) * 10,
(y + 1) * 10,
outline="red",
fill="#ffffff",
)
for x in range(30)
]
for y in range(20)
]
# Bouton de contrôle
bouton = Button(fen, text="Crible !", command=action_bouton)
bouton.pack()
fen.mainloop()
Projet 1 : color picker
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui propose trois échelles graduées de 0 à 255 pour choisir les composantes de Rouge, Vert et Bleu et affiche le codage en hexadécimal #RRVVBB de la couleur (R, V, B) et un canevas rectangulaire colorié avec cette couleur
Résultat attendu
- Scale
- Label
- Canvas
- Variables de contrôle
- Contrôleur pour l'événement
"<Motion>"
de déplacement avec la souris :
fen.bind_all("<Motion>", changer_canevas) # fen étant la fenêtre racine
- Codage d'un entier positif en base 16 (hexadécimal) : voir page 7 du Cours sur la représentation des entiers
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
#%% Fonctions
CHIFFRES_HEX = "0123456789ABCDEF"
def dec_to_hex(d):
"""Convertit un entier compris entre 0 et 255
en un nombre hexadécimal sur deux chiffres
Renvoie une chaîne de caractères
"""
# à compléter
def test_dec_to_hex():
assert dec_to_hex(4) == "04"
assert dec_to_hex(10) == "0A"
assert dec_to_hex(255) == "FF"
assert dec_to_hex(166) == "A6"
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Color_picker")
fen.geometry("400x500")
# Echelle de graduation du rouge
rouge = IntVar()
graduation_rouge = Scale(
fen,
orient="horizontal",
from_=0,
to=255,
resolution=1,
tickinterval=50,
length=350,
variable=rouge,
label="Rouge",
)
graduation_rouge.pack()
# Echelle de graduation du vert
# à compléter
# Echelle de graduation du bleu
# à compléter
# Code HTML de la couleur
# à compléter
# Canevas d'affichage de la couleur
# à compléter
#%% Modèle :
def changer_canevas(event):
# à compléter
# ne pas oublier de mettre à jour l'affichage de la fenêtre
fen.update()
#%% Controleur
fen.bind_all("<Motion>", changer_canevas)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 2 : chiffrement de César
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui permet de saisir un mot source en lettre capitale, de sélectionner un décalage entre 0 et 25 et d'appuyer sur un bouton pour afficher le chiffrement du mot source avec l'algorithme de chiffrement par décalage de César.
Source : Wikipedia Le chiffre de César fonctionne par décalage des lettres de l'alphabet. Par exemple dans l'image ci-dessus, il y a une distance de 3 caractères, donc B devient E dans le texte codé.
Résultat attendu
-
Codage des caractères, voir le cours. Vous aurez surtout besoin des fonctions
chr
etord
:🐍 Script Python>>> ord('A') 65 >>> chr(65) 'A' >>> chr(ord('A') + 3) 'D' >>> chr(ord('A') + (3 + 26) % 26) 'D'
-
Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
#%% Modèle
def chiffrement():
"""
Fonction de rappel appelée par un appui sur le bouton bouton_chiffre
Returns
-------
Modifie le contenu de la variable de contrôle mot_chiffre
avec le chiffrement de la variable de contrôle mot_source
par décalage donné par la variable de contrôle decalage
"""
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Chiffrement de César")
fen.geometry("600x400")
# Cadre source
cadre_source = Frame(fen)
etiq_source = Label(cadre_source, text="Mot source : ", font=("Arial", 20))
mot_source = StringVar()
saisie_source = Entry(
cadre_source, textvariable=mot_source, font=("Arial", 20)
)
etiq_source.pack(side=LEFT)
saisie_source.pack(side=LEFT)
cadre_source.pack(anchor=W)
# Cadre décalage
cadre_decalage = Frame(fen)
decalage = IntVar()
# à compléter
# Cadre bouton
cadre_bouton = Frame(fen)
# à compléter
# Cadre chiffre
cadre_chiffre = Frame(fen)
mot_chiffre = StringVar()
# à compléter
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 3 : alternatif (jeu)
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui présente une grille carrée avec un des cases noires délimitées par des traits rouges. Lorsque le joueur clique sur une case il change sa couleur ainsi que celle des cases de son voisinage vertical ou horizontal. La couleur d'une case peut alterner entre noir et blanc. Le but du jeu est de colorier toute la grille en blanc.
Résultat attendu
- Canvas
- Rectangle dessiné dans un canevas
- Contrôleur pour l'événement
"<ButtonPress-1>"
clic gauche de la souris :
fen.bind_all("<ButtonPress-1>", clic) # fen étant la fenêtre racine
- liste de listes pour créer une grille de rectangles
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
### Constantes
COTE_CASE = 100 # coté d'une case en pixels
NB_LIG = 4 # nombre de lignes
NB_COL = 4 # nombre de colonnes
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("ALTERNATIF JEU")
# Canevas
# à compléter
# grille de rectangles dessinés dans le canevas
# à compléter
#%% Modèle
def voisinage(x, y):
"""
Renvoie la liste des coordonnées des cases voisines
de la case de coordonnées (x, y)
Parameters
----------
x : abscisse de type int
y : ordonnée de type int
Returns
-------
lis : liste des coordonnées des cases voisines
"""
# à compléter
def clic(event):
"""
Gestionnaire de l'événement clic avec bouton gauche sur une case
"""
# à compléter
# ne pas oublier de mettre à jour la fenêtre
fen.update()
#%% Controleur
fen.bind_all("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 4 : calculateur d'adresses IP
Niveau de difficulté
Difficulté moyenne mais long, 200 lignes de codes voir plus.
Cahier des charges
Réaliser une interface graphique en tkinter qui présente :
- deux widgets d'entrée : un champ de saisie d'adresse IP en notation décimale pointée et une échelle de sélection de la longueur du masque réseau
- plusieurs widgets de sortie de type étiquettes avec l'affichage :
- de l'adresse du réseau
- de la première et de la dernière adresse de machine du réseau
- du nombre d'adresses de machines disponibles dans ce réseau
- de l'adresse de
broadcast
(dernière adresse de la plage)
Modèle de l'outil : Calculateur de masque réseau du CRIC de Grenoble
Résultat attendu
- Codage en base deux et conversion d'une écriture en base dix sous la forme d'un entier à une écriture en base deux sous forme de liste de bits (0 ou 1). Voir le cours sur la représentation des entiers
- Format des adresses IPV4 : voir le Point de Cours 5 du Cours sur les Réseaux
- Listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
On propose plusieurs fonctions outils : pour passer de l'écriture décimale d'un entier en base dix à son écriture en base deux sous forme de liste de bits et vice-versa etc ...
from tkinter import *
#%% Modèle
def dec_to_bits(dec):
"""
Convertit en binaire un entier
Parameters
----------
dec :un entier entre 0 et 255
Returns
-------
Représentation binaire de l'entier sous forme de liste de 8 bits
"""
bits = [0 for i in range(8)]
# à compléter
return bits
def test_dec_to_bits():
assert dec_to_bits(15) == [0, 0, 0, 0, 1, 1, 1, 1]
assert dec_to_bits(132) == [1, 0, 0, 0, 0, 1, 0, 0]
def bits_to_dec(bits):
"""
Convertit une liste de bits en décimal
Parameters
----------
bits : liste de 8 bits
Returns
-------
entier entre 0 et 255
"""
# à compléter
def test_bits_to_dec():
assert bits_to_dec([0, 0, 0, 0, 1, 1, 1, 1]) == 15
assert bits_to_dec([1, 0, 0, 0, 0, 1, 0, 0]) == 132
def ip_to_bits(ip):
"""
Convertit une adresse ip sous forme de str en liste de 32 bits
Parameters
----------
ip : adresse ip sous forme de str comme '172.17.232.6'
Returns
-------
adresse ip sous forme de liste de 32 bits
"""
# à compléter
def test_ip_to_bits():
assert ip_to_bits("172.17.232.6") == [
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
]
def bits_to_ip(ip_bits):
"""
Convertit une adresse ip sous forme de str en liste de 32 bits
Parameters
----------
ip_bits : adresse ip sous forme de liste de 32 bits
Returns
-------
adresse ip sous forme de str comme '172.17.232.6'
"""
# à compléter
def test_bits_to_ip():
assert (
bits_to_ip(
[
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
]
)
== "172.17.232.6"
)
def verifier_ip(ip):
"""
Vérifie si une adresse ip sous forme de str comme '172.17.232.6'
est valide
Parameters
----------
ip : adresse ip sous forme de str comme '172.17.232.6'
Returns
-------
booléen
"""
# à compléter
def test_verifier_ip():
assert verifier_ip("172.17.232.6") == True
assert verifier_ip("172.17.256.6") == False
assert verifier_ip("172.17.256") == False
assert verifier_ip("172.17.256") == False
def prefixe_reseau(ip_bits, mask):
"""
Renvoie le préfixe réseau d'une adresse IP sous forme de liste de 32 bits
à partir de la longueur du masque réseau
Parameters
----------
ip_bits : liste de bits (0 ou 1)
masque : entier entre 1 et 31 bits
Returns
-------
Renvoie une liste de 32 bits
"""
prefixe = [0 for i in range(32)]
ip_masque = [1 for i in range(mask)] + [0 for j in range(32 - mask)]
# à compléter
def test_prefixe_reseau():
assert prefixe_reseau(
[
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
],
21,
) == [
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
def calcul_ip():
"""Fonction de rappel du bouton bouton_calcul
Récupère l'adresse IP de la variable de contrôle adresse_ip
Vérifie si cette adresse est valide avec verifier_ip
Si elle est valide, convertit l'adresse en liste de 32 bits,
puis calcule le préfixe réseau, la première adresse, la dernière adresse
et l'adresse de broadcast (dernière adresse de la plage)
Modifie pour cela les variables de contrôles liées aux étiquettes
d'affichage de ces différents éléments
"""
ip = adresse_ip.get()
if not verifier_ip(ip):
message_erreur.set("Adresse non conforme")
else:
message_erreur.set("")
ip_bits = ip_to_bits(ip)
adresse = prefixe_reseau(ip_bits, masque.get())
adresse_reseau.set("Adresse réseau : " + bits_to_ip(adresse))
adresse[31] = 1
premiere_adresse.set("Première adresse : " + bits_to_ip(adresse))
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Calculateur d'adresses IP")
fen.geometry("600x400")
### Variables de contrôles
adresse_ip = StringVar()
premiere_adresse = StringVar()
premiere_adresse.set("Première adresse : ")
adresse_reseau = StringVar()
adresse_reseau.set("Adresse réseau : ")
derniere_adresse = StringVar()
derniere_adresse.set("Dernière adresse : ")
adresse_broadcast = StringVar()
adresse_broadcast.set("Adresse broadcast : ")
message_erreur = StringVar()
nombre_adresses_ip = StringVar()
nombre_adresses_ip.set("Nombre d'adresses IP : ")
masque = IntVar()
# Cadre adresse
cadre_adresse = Frame(fen)
etiq_adresse = Label(cadre_adresse, text="Adresse IP : ", font=("Arial", 20))
saisie_adresse = Entry(
cadre_adresse, textvariable=adresse_ip, font=("Arial", 20)
)
etiq_adresse.pack(side=LEFT)
saisie_adresse.pack(side=LEFT)
cadre_adresse.pack()
# Cadre masque
cadre_masque = Frame(fen)
# à compléter
# Cadre calcul
cadre_calcul = Frame(fen)
# à compléter
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 5 : jeu de pendu
Niveau de difficulté
Difficile, 200 lignes de codes voir plus.
Cahier des charges
Réaliser une interface graphique en tkinter pour un jeu de pendu avec :
- un alphabet de 26 lettres cliquables, chaque lettre déjà sélectionnée s'affichant en rouge
- un bouton pour choisir un nouveau mot secret (dans le fichier dico_pendu.txt que vous téléchargerez)
- un affichage des lettres du mot secret sous la forme de rectangles qui font apparaître les lettres découvertes
- un champ de saisie du mot complet proposé par le joueur et en dessous un bouton pour valider sa saisie
- une étiquette d'affichage des messages : nombre d'esssais restants (10 au départ), résultat et révélation du mot en fin de partie
Résultat attendu
- Canvas
- Rectangle dessiné dans un canevas
- Texte dessiné dans un canevas
- Label
- Button
- Entry
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import random
#%% Constantes
# Récupération de la listes des mots
f = open("dico_pendu.txt")
LISTE_MOTS = [ligne.rstrip() for ligne in f]
f.close()
#%% Variables globales
mot_secret = (
"SECRET" # valeur initiale du mot secret au lancement de l'interface
)
nb_essais = 0 # nombre d'essais
jouer = False # un jeu est en cours
#%% Modèle
def clic(event):
"""Gestionnaire de clic sur une lettre de l'alphabet"""
global selection, nb_essais, nb_carac_restants, jouer
if jouer:
x = (
event.x
) // 50 # si on considère qu'on trace les lettres dans des carrés de coté 50 pixels (à voir)
y = (event.y) // 50
# à compléter
def choix_mot():
"""Fonction de rappel pour le bouton nouveau_mot"""
global mot_secret, lettre_mot, jouer, nb_carac_restants, nb_essais
jouer = True
can2.delete("all")
can1.delete("all")
generer_alphabet()
# à compléter
# on n'oublie pas de rafraichir la fenêtre
fen.update()
def valider_proposition():
"""Fonction de rappel pour le bouton bouton_proposition"""
global jouer, proposition_mot, nb_essais
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Pendu")
fen.geometry("1000x900")
# Consigne
consigne = Label(
fen,
text="Choisissez une lettre en cliquant dessus",
font=("Arial Bold", 30),
pady=10,
)
consigne.pack()
# Cadre pour l'alphabet
cadre_alphabet = Frame(fen)
can1 = Canvas(cadre_alphabet)
def generer_alphabet():
"""Dessine un nouvel alphabet dans le canevas can1"""
global alphabet, selection
# liste de listes des lettres de l'alphabet qui seront dessinées dans des rectangles
alphabet = [[None for x in range(7)] for y in range(4)]
# liste de listes de boolennes indiquant pour chaque lettre si elle a été sélectionnée
selection = [[False for x in range(7)] for y in range(4)]
# à compléter
generer_alphabet()
can1.pack()
cadre_alphabet.pack()
# Cadre pour le mot à deviner
cadre_mot = Frame(fen)
etiq_mot = Label(
cadre_mot, text="Mot à deviner", font=("Arial Bold", 30), pady=10
)
nouveau_mot = Button(
cadre_mot, text="Nouveau mot", font=("Arial Bold", 30), command=choix_mot
)
# les cases des lettres du mot à deviner seront dessinées dans le Canevas can2
# après appel de la fonction choix_mot
can2 = Canvas(cadre_mot, height=200)
# à compléter
# Etiquette du champ de saisie saisie_mot
# Champe d saisie saisie_mot
# Bouton bouton_proposition
# Essais
msg_essais = StringVar()
# à compléter
# Etiquette de message au joueur
#%%Controleurs
can1.bind("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 6 : jeu de la vie, automate cellulaire en deux dimensions
Niveau de difficulté
Moyennement difficile, entre 150 et 200 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter pour un jeu de la vie, automate cellulaire en deux dimensions.
- une grille de cases blanches délimitées pas un bord rouge : chaque case représente une cellule, morte si blanche et vivante si noire
- la possibilité d'initialiser une configuration en cliquant sur chaque case pour changer sa couleur de blanc à noir ou vice versa
- un bouton pour lancer le jeu et l'affichage des générations successives par application des trois règles ci-dessous du jeu de la vie. Lorsque le jeu est lancé il n'est plus possible de changer la couleur des cases.
- un bouton pour arrêter le jeu en cours
Jeu de la vie
Le jeu de la vie inventé par le mathématicien anglais J. H. Conway en 1970, est un automate cellulaire en deux dimensions.
Les automates cellulaires et le jeu de la vie en particulier sont des systèmes complexes étudiés en mathématiques et en informatique théorique. Ainsi, l'automate de Conway a été prouvé turing-complet c'est-à-dire qu'il peut exécuter les mêmes algorithmes qu'un ordinateur.
L'univers du jeu est une grille infinie de cases appelées cellules, en pratique on utilise une grille finie dont les bords se rejoignent (haut <-> bas et gauche <-> droite) : il suffit d'effectuer les calculs de voisinage modulo le nombre de lignes ou de colonnes.
Au départ un nombre fini de cellules sont vivantes et toutes les autres sont mortes.
Chaque cellule possède un voisinage de 8 cellules voisines (sauf les cellules sur les bords dans notre simulation).
A chaque génération, l'univers évolue selon trois règles simples de changement d'état pour chaque cellule :
- Règle 1 : Une cellule vivante qui n'a pas au moins 2 voisines vivantes meurt par isolement.
- Règle 2 : Une cellule vivante qui possède 4 voisines vivantes ou plus meurt par étouffement.
- Règle 3 : Une cellule morte qui possède exactement trois cellules vivantes, devient vivante, sinon elle reste morte.
Résultat attendu
La configuration iniitiale est celle d'un planeur.
- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes de listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import time
# CONSTANTES
COTE_CASE = 15
NB_LIG = 50
NB_COL = 50
VIVANTE = "#000000"
MORTE = "#ffffff"
VOISINAGE = [
(-1, 0),
(-1, -1),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
]
# Variables globales
jouer = False
generation = 0
## Modèle
def clic(event):
"""Gestionnaire de clic avec le bouton gauche de la souris
sur une case"""
if not jouer:
x = event.x // COTE_CASE
y = event.y // COTE_CASE
# à compléter
# on n'oublie pas de mettre à jour la fenêtre
fen.update()
def nb_voisins_vivants(x, y):
"""
Renvoie le nombe de voisins vivants de la case
de coordonnées (x, y)
"""
# à compléter
def vie():
"""
Permet de changer la variable globale jouer
avant de lancer le jeu de la vie
"""
global jouer
jouer = True
jeu_vie()
def jeu_vie():
"""
Lance un jeu de la vie à partir de la configuration de la grille
"""
global generation
while jouer:
generation = generation + 1
# à compléter
# on n'oublie pas de mettre à jour la fenêtre
fen.update()
# on attend 0.1 secondes entre chaque génération
time.sleep(0.1)
def mort():
global jouer
jouer = False
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("JEU DE LA VIE")
# Canevas de dessin de la grille
can = Canvas(fen, width=COTE_CASE * NB_COL, height=COTE_CASE * NB_LIG)
can.pack()
# à compléter
### Controleur
can.bind("<ButtonPress-1>", clic)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 7 : automate cellulaire à une dimension
Niveau de difficulté
Difficile, entre 150 et 200 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter pour un automate cellulaire à une dimension.
- une grille de cases blanches délimitées pas un bord rouge : chaque case représente une cellule, morte si blanche et vivante si noire
- la possibilité d'initialiser une configuration en cliquant sur chaque case de la première ligne pour changer sa couleur de blanc à noir ou vice versa
- un champ de saisie du numéro de la règle
- un bouton pour lancer le jeu et l'affichage des générations successives sur les lignes suivantes
- un bouton pour réinitialiser le jeu en cours : il efface toutes les cases noires
Automate cellulaire à une dimension
Prenons la règle numéro 90. On commence par écrire 90 en base deux sur 8 bits
Poids \(2^{7}\) | Poids \(2^{6}\) | Poids \(2^{5}\) | Poids \(2^{4}\) | Poids \(2^{3}\) | Poids \(2^{2}\) | Poids \(2^{1}\) | Poids \(2^{0}\) |
---|---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
Ensuite, considérons une case de la première ligne. Elle peut être vivante (codage 1) ou morte (codage 0) et de même pour sa voisine de gauche (on repart de l'autre bout avec un modulo pour la case la plus à gauche) ou de droite (on repart au début pour la case la plus à droite). Il existe donc \(2^{3} = 8\) triplets de 0 ou 1 représentant tous les états possibles de ce voisinage de trois cases (gauche, case considérée, droite). Par exemple si (gauche, case considérée, droite) = (1 ,1 0), vu comme une écriture en base deux cela donne \(1 \times 2^{2}+1 \times 2^{1}+ 0 \times 2^{0}= 6\). Le coefficient du poids de \(2^{6}\) dans l'écriture binaire du numéro de règle \(90\) est \(1\). Ce sera l'état de la cellule en dessous de la case considérée. On procède de même pour chaque valeur du triplet (gauche, case considérée, droite) : on le regarde comme une écriture binaire d'un entier \(k\) entre 0 et 7, on lit la valeur du coefficient de \(2^{k}\) dans l'écriture binaire du numéro de la règle et on obtient l'état de la case du dessous. Tout repose sur le codage binaire !
Résultat attendu
Exemple des premières générations pour la règle numéro 90.
- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes de listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import time
#%% Constantes et variables globales
# CONSTANTES
COTE_CASE = 20
NB_LIG = 31
NB_COL = 31
VIVANTE = "#000000"
MORTE = "#ffffff"
# Variables globales
action = False
#%% Fonctions
def dec_to_bits(n):
"""
Convertit en binaire un entier
Parameters
----------
dec :un entier entre 0 et 255
Returns
-------
Représentation binaire de l'entier sous forme de liste de 8 bits
"""
# précondition
assert isinstance(n, int) and 0 <= n < 256
t = [0 for _ in range(8)]
# à compléter
return t
def test_dec_to_bits():
assert dec_to_bits(15) == [0, 0, 0, 0, 1, 1, 1, 1]
assert dec_to_bits(132) == [1, 0, 0, 0, 0, 1, 0, 0]
def generer_table(regle):
"""
Renvoie la table de correspondance pour un numéro de règle entre 0
et 255. C'est la liste des bits de l'écriture binaire du numéro
"""
return dec_to_bits(regle)
def cellule_dessous(gauche, milieu, droite, table):
"""
Renvoie l'état 1 pour vivant ou 0 pour mort de la cellule en dessous
de la cellule du milieu en fonction des états (1 ou 0) de la cellule
du milieu, de sa voisine de gauche et de celle de droite
"""
# à compléter
#%% Modèle
def clic(event):
"""Gestionnaire de clic avec le bouton gauche sur une case
de la première ligne (lesautres cases ne peuvent être sélectionnées)"""
# à compléter
def vie():
"""Met à jour la variable globale action avant de lancer l'automate"""
global action
action = True
automate()
def automate():
"""
Lance l'automate et génère les lignes suivantes
à la vitesse de 1 ligne par seconde
"""
# à compléter
def reset():
"""Met à jour la variable globale action avant de lancer l'automate"""
global action
action = False
can.delete(ALL)
# à compléter : il faut redessiner toutes les cases blanches à bords rouges
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("AUTOMATE CELLULAIRE")
# Canevas
can = Canvas(fen, width=COTE_CASE * NB_COL, height=COTE_CASE * NB_LIG)
can.pack()
# à compléter
# Cadre de commande
cadre = Frame(fen)
cadre.pack()
# Bouton de lancement de l'automate
# à compléter
# Bouton de réinitialisation (reset)
# à compléter
# Etiquette de règle
etiq_regle = Label(cadre, text="Numéro de règle :")
etiq_regle.pack()
# Champ de saisie de règle
regle = StringVar()
saisie_regle = Entry(cadre, textvariable=regle)
saisie_regle.pack()
#%% Controleur
can.bind("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()