Aller au contenu

Ressources⚓︎

Tutoriels Tkinter

Deux tutoriels de présentation du module d'interface graphique tkinter :

Sitographie de sites resssources sur tkinter :

Introduction⚓︎

Téléchargement des scripts à compléter des exercices 1 à 4

Exercice 1

  1. Télécharger le script hello_world.py et l'exécuter dans l'IDE Spyder.

    code source
    🐍 Script Python
    from 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ée objet.methode()
      • en dernière instruction du programme, il faut lancer la méthodemainloop 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.
  2. Remplacer msg = Label(fen, text="Hello World") par msg = Label(fen, text="Hello World", font=("Arial", 20)). Exécuter, observer.

  3. 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 Python
    fen.geometry("300x50")
    fen.resizable(width=False, height=False)
    

Exercice 2

  1. Télécharger le script controle_barre.py et l'exécuter dans l'IDE Spyder.

    Code source
    🐍 Script Python
    from 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 :

    alt

    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énement event 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éthode coords du canevas. Après avoir modifié l'objet graphique, il faut mettre à jour l'affichage de toute la fenêtre racine avec fen.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.

    alt

    Partie Contrôleur

    Le réceptionnaire d'événements de la boucle mainloop() capturetous les événements (appui touche, mouvement de la souris). L'instruction fen.bind("<KeyPress>", deplacement) met en place un contrôleur qui va appeler l'exécution de la fonction deplacement (partie Modèle) si l'événement Appui sur une touche est capturé.

  2. Pourquoi faut-il retrancher DY si on veut déplacer verticalement la barre vers le haut ?

  3. 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 ?
  4. Dans le programme on utilise deux variables globales x_barre et y_barre. Quelle est la différence avec la variable locale var_locale ? A quoi sert le mot clef global dans une fonction ?
  5. 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.
  6. 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 fonctions min et max. Par exemple pour que la barre ne puisse pas sortir par le bas du canevas, on peut écrire :

    🐍 Script Python
    y_barre = max(0, y_barre - DY)
    

Exercice 3

  1. Télécharger le script clic_ballon.py et l'exécuter dans l'IDE Spyder.

    Code source
    🐍 Script Python
    from 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()
    
  2. Quel est le type de la valeur renvoyée par la fonction from_rgb ?

  3. Insérer print(help(from_rgb)) avant fen.mainloop(). Que s'affiche-t-il dans la console interactive ?
  4. Dans la fonction from_rgb a quoi servent les instructions assert ?
  5. 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.
  6. Qu'est-ce que le codage (Rouge, Vert, Bleu) d'une couleur ?
  7. Dans la console interactive afficher l'aide de la fonction randint du module random (importé au début du programme) avec help(random.randint). Modifier alors la fonction ballon 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

  1. Télécharger le script mention.py et l'exécuter dans l'IDE Spyder.

    Code source
    🐍 Script Python
    from 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 dans fen.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 Python
      saisie_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 Python
      moyenne = 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 Python
      moyenne = 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 fonction calcul_mention et la variable moyenne est lue et la variable mention est modifiée.

  2. 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
🐍 Script Python
from tkinter import *

fen = Tk()
msg = Label(fen, text="Hello World")
msg.pack()
fen.mainloop()
Code source controle_barre.py
🐍 Script Python
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
🐍 Script Python
#!/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
🐍 Script Python
#!/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⚓︎

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

alt

🐍 Script Python
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.
🐍 Script Python
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.

alt

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

alt

  • Codage des caractères, voir le cours. Vous aurez surtout besoin des fonctions chr et ord :

    🐍 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.

🐍 Script Python
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

alt

🐍 Script Python
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.
🐍 Script Python
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

alt

  • 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 ...

🐍 Script Python
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

alt

  • Listes et chaînes de caractères
  • Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
🐍 Script Python
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).

voisinage

A chaque génération, l'univers évolue selon trois règles simples de changement d'état pour chaque cellule :

  1. Règle 1 : Une cellule vivante qui n'a pas au moins 2 voisines vivantes meurt par isolement.
  2. Règle 2 : Une cellule vivante qui possède 4 voisines vivantes ou plus meurt par étouffement.
  3. 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.

alt

  • Listes de listes et chaînes de caractères
  • Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
🐍 Script Python
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.

alt

  • Listes de listes et chaînes de caractères
  • Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
🐍 Script Python
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()