Aller au contenu

TP — Prise en main de Tkinter : une cible interactive⚓︎

Objectifs⚓︎

🎯 Objectifs

Dans ce TP, on réalise progressivement une petite application graphique représentant une cible.

L'utilisateur peut :

  • cliquer dans la zone de dessin pour placer un impact ;
  • marquer un nombre de points qui dépend de la position de l'impact ;
  • consulter son score total ;
  • effacer tous les impacts à l'aide d'un bouton.

Ce TP permet de découvrir :

  • la structure d'un programme utilisant Tkinter ;
  • quelques composants graphiques appelés widgets ;
  • le dessin de formes géométriques dans un Canvas ;
  • l'association d'une fonction à un bouton ;
  • la gestion d'un événement provoqué par un clic de souris.

Prérequis

Variables, expressions, instructions conditionnelles, boucles, fonctions et listes.


1. Une première fenêtre⚓︎

Exercice 1

Tkinter est une bibliothèque qui permet de créer des interfaces graphiques en Python. Elle est installée avec la plupart des distributions de Python.

Recopier puis exécuter le programme suivant dans un environnement de programmation Python (Spyder, Thonny, Pyzo ...).

🐍 Script Python
from tkinter import *

# Création de la fenêtre principale
fenetre = Tk()
fenetre.title("Cible interactive")

# Création d'un texte dans la fenêtre
titre = Label(fenetre, text="Cliquez sur la cible !")
titre.pack()

# Lancement de la boucle de gestion des événements
fenetre.mainloop()

Résultat attendu

alt

Question 1

Repérer l'instruction qui crée la fenêtre principale.

Question 2

Modifier le titre affiché dans la barre supérieure de la fenêtre.

Question 3

Modifier le texte du widget Label.

Question 4

Commenter temporairement la dernière instruction, puis exécuter de nouveau le programme. Que constate-t-on ?

La structure d'un programme Tkinter

Un programme Tkinter comporte généralement quatre étapes :

  1. importer la bibliothèque Tkinter ;
  2. créer la fenêtre principale avec Tk() ;
  3. créer et placer les widgets dans cette fenêtre ;
  4. lancer la boucle événementielle avec mainloop().

L'appel à mainloop() place le programme en attente. Tkinter surveille alors les actions de l'utilisateur : clic sur un bouton, clic dans le canevas, appui sur une touche, fermeture de la fenêtre, etc.

On parle de programmation événementielle : l'ordre d'exécution dépend en partie des événements provoqués par l'utilisateur.

Créer et placer un widget

L'instruction suivante crée un widget de type Label contenu dans fenetre :

🐍 Script Python
titre = Label(fenetre, text="Cliquez sur la cible !")

L'instruction titre.pack() demande ensuite à Tkinter de placer ce widget dans la fenêtre.

La méthode pack() empile les widgets les uns à la suite des autres. Elle suffit pour les interfaces simples de ce TP.


2. Créer une zone de dessin⚓︎

Exercice 2

On souhaite placer sous le titre une zone de dessin de 600 pixels de largeur et 400 pixels de hauteur.

Ajouter les constantes suivantes au début du programme, après l'importation de Tkinter.

🐍 Script Python
LARGEUR = 600
HAUTEUR = 400

Ajouter ensuite les instructions suivantes avant l'appel à mainloop().

🐍 Script Python
canvas = Canvas(
    fenetre,
    width=LARGEUR,
    height=HAUTEUR,
    background="#dce8f2"
)
canvas.pack()

Résultat attendu

alt

Question 1

Exécuter le programme et vérifier que la zone de dessin apparaît sous le titre.

Question 2

Modifier la couleur de fond du canevas.

Question 3

Remplacer temporairement LARGEUR par 400. Quelle dimension de la zone est modifiée ?

Le widget Canvas

Un Canvas, appelé aussi canevas, est une zone dans laquelle on peut dessiner des formes géométriques, du texte ou des images.

Son origine est située dans son coin supérieur gauche.

  • l'abscisse x augmente vers la droite ;
  • l'ordonnée y augmente vers le bas.

Ainsi, le point de coordonnées (0, 0) est le coin supérieur gauche du canevas.


3. Dessiner la cible⚓︎

Exercice 3 : dessiner un premier disque

On choisit pour centre de la cible le point de coordonnées (300, 200).

Ajouter les constantes suivantes au début du programme.

🐍 Script Python
X_CENTRE = LARGEUR // 2
Y_CENTRE = HAUTEUR // 2

Pour dessiner un disque de rayon 150 pixels, ajouter avant mainloop() et après la création du Canevas :

🐍 Script Python
canvas.create_oval(
    X_CENTRE - 150,
    Y_CENTRE - 150,
    X_CENTRE + 150,
    Y_CENTRE + 150,
    fill="white",
    outline="black"
)

Résultat attendu

alt

Question 1

Exécuter le programme et vérifier que le disque est centré dans le canevas.

Question 2

Les quatre premières valeurs transmises à create_oval sont les coordonnées de deux points. Quels sommets du rectangle encadrant le disque représentent-elles ?

Question 3

Modifier le rayon du disque pour obtenir un disque de rayon 120 pixels.

Dessiner dans un Canvas

Les méthodes suivantes permettent de dessiner quelques formes usuelles :

🐍 Script Python
canvas.create_rectangle(x1, y1, x2, y2, fill="blue")
canvas.create_oval(x1, y1, x2, y2, fill="red")
canvas.create_line(x1, y1, x2, y2, fill="black")
canvas.create_text(x, y, text="Bonjour")

Pour un rectangle ou un ovale, (x1, y1) et (x2, y2) sont deux sommets opposés du rectangle qui délimite la forme.

Exercice 4 : trois disques concentriques

On veut obtenir trois disquesde rayons 150, 100 et 50 pixels, colorées respectivement en blanc, rouge et jaune.

Résultat attendu

alt

Question 1

  1. Compléter la fonction suivante et insérer son code juste avant la création de la fenêtre.
🐍 Script Python
def dessiner_cible():
    canvas.create_oval(
        X_CENTRE - 150, Y_CENTRE - 150,
        X_CENTRE + 150, Y_CENTRE + 150,
        fill="white", outline="black"
    )

    # À compléter : disque rouge de rayon 100

    # À compléter : disque jaune de rayon 50

Question 2

Supprimer le premier appel isolé à canvas.create_oval, puis appeler dessiner_cible() avant mainloop().

Question 3

Ajouter deux lignes noires, l'une horizontale et l'autre verticale, passant par le centre de la cible.

Ordre des dessins

Les formes sont dessinées les unes au-dessus des autres dans leur ordre de création. Il faut donc dessiner le plus grand disque en premier et le plus petit en dernier.


4. Réagir à un clic dans le canevas⚓︎

Exercice 5

Ajouter la fonction suivante avant la création des widgets.

🐍 Script Python
def tirer(event):
    x = event.x
    y = event.y
    print("Clic aux coordonnées", x, y)

Puis ajouter l'instruction suivante après la création du canevas :

🐍 Script Python
canvas.bind("<Button-1>", tirer)

Question 1

Cliquer à plusieurs endroits dans le canevas et observer la console.

Question 2

Cliquer près du coin supérieur gauche, puis près du coin inférieur droit. Vérifier que les coordonnées affichées sont cohérentes avec le repère du canevas.

Question 3

Quelle instruction associe l'événement « clic gauche » à la fonction tirer ?

Un gestionnaire d'événement

La méthode bind associe un événement à une fonction :

🐍 Script Python
canvas.bind("<Button-1>", tirer)

Lorsque l'utilisateur clique dans le canevas, Tkinter appelle automatiquement tirer(event).

Le paramètre event est un objet créé par Tkinter. Il contient des informations sur l'événement. Pour un clic de souris :

  • event.x contient l'abscisse du clic ;
  • event.y contient l'ordonnée du clic.

Dessiner un impact⚓︎

Exercice 6

On représente un impact par un petit disque noir de rayon 4 pixels.

Question 1

Compléter la fonction tirer afin qu'un impact apparaisse à l'endroit du clic.

🐍 Script Python
def tirer(event):
    x = event.x
    y = event.y

    canvas.create_oval(
        # coordonnées à compléter
        fill="black",
        outline="black"
    )

Question 2

Tester le programme en plaçant plusieurs impacts.

Question 3

Modifier la taille et la couleur des impacts.


5. Calculer le score d'un tir⚓︎

Exercice 7

Le nombre de points dépend de la distance entre l'impact et le centre de la cible :

Zone atteinte Nombre de points
disque jaune 5
disque rouge 3
disque blanc 1
extérieur de la cible 0

Pour éviter de calculer une racine carrée, on compare le carré de la distance au carré du rayon :

\[ d^2=(x-X_{\text{centre}})^2+(y-Y_{\text{centre}})^2. \]

Question 1

Compléter la fonction suivante et insérer son code juste avant la fonction tirer.

🐍 Script Python
def calculer_points(x, y):
    distance_carre = (x - X_CENTRE) ** 2 + (y - Y_CENTRE) ** 2

    if distance_carre <= 50 ** 2:
        return 5
    # À compléter pour les trois autres cas

Question 2

Pourquoi faut-il tester la zone jaune avant la zone rouge ?

Question 3

Donner les résultats des appels suivants sans les exécuter, puis vérifier vos réponses dans la console.

🐍 Script Python
calculer_points(300, 200)
calculer_points(360, 200)
calculer_points(420, 200)
calculer_points(500, 200)

6. Afficher et mettre à jour le score⚓︎

Exercice 8

Ajouter les variables globales suivantes avant la définition des fonctions.

🐍 Script Python
score_total = 0
texte_score = None

Après la création de la fenêtre, remplacer la valeur None par une variable Tkinter et créer un nouveau Label :

🐍 Script Python
texte_score = StringVar()
texte_score.set("Score : 0")

label_score = Label(fenetre, textvariable=texte_score)
label_score.pack()

Résultat attendu

alt

Question 1

Remplacer la fonction tirer par la version suivante, puis compléter les deux lignes indiquées.

🐍 Script Python
def tirer(event):
    global score_total

    x = event.x
    y = event.y

    canvas.create_oval(
        x - 4, y - 4,
        x + 4, y + 4,
        fill="black",
        outline="black"
    )

    points = ...                         # à compléter
    score_total = ...                    # à compléter
    texte_score.set("Score : " + str(score_total))

Question 2

Tester plusieurs tirs et vérifier le cumul des points.

Question 3

Expliquer pourquoi la conversion str(score_total) est nécessaire.

Question 4

Expliquer le rôle de l'instruction global score_total.

Une variable Tkinter associée à un widget

Une StringVar contient un texte susceptible de changer pendant l'exécution du programme.

🐍 Script Python
texte_score = StringVar()
texte_score.set("Score : 0")
label_score = Label(fenetre, textvariable=texte_score)

Lorsque la méthode set modifie le contenu de texte_score, le texte affiché par le Label est automatiquement actualisé.


7. Commander une action avec un bouton⚓︎

Exercice 9

On souhaite effacer tous les impacts et remettre le score à zéro. Pour cela, il faut d'abord mémoriser dans une liste les identifiants des impacts créés.

Ajouter cette variable globale avant la définition des fonctions :

🐍 Script Python
impacts = []

La méthode create_oval renvoie un entier qui identifie la forme créée dans le canevas.

Dans la fonction tirer, remplacer l'appel à create_oval par :

🐍 Script Python
impact = canvas.create_oval(
    x - 4, y - 4,
    x + 4, y + 4,
    fill="black",
    outline="black"
)
impacts.append(impact)

Compléter la fonction suivante et l'insérer après la fonction tirer.

🐍 Script Python
def effacer_impacts():
    global score_total

    for impact in impacts:
        # Supprimer du canevas la forme identifiée par impact
        ...

    impacts.clear()      # vider la liste `impacts`
    score_total = ...                    # à compléter
    texte_score.set(...)                 # à compléter

Pour supprimer une forme du canevas à partir de son identifiant, on utilise :

🐍 Script Python
canvas.delete(identifiant)

Créer le bouton après le canevas et avant mainloop().

🐍 Script Python
bouton_effacer = Button(
    fenetre,
    text="Effacer les impacts",
    command=effacer_impacts
)
bouton_effacer.pack()

Résultat attendu

alt

Question 1

Tester le bouton après avoir placé plusieurs impacts.

Question 2

Dans l'argument command, pourquoi écrit-on effacer_impacts et non effacer_impacts() ?

Le paramètre command d'un Button

Le paramètre command reçoit la fonction que Tkinter devra appeler lors d'un clic sur le bouton.

🐍 Script Python
Button(fenetre, text="Effacer", command=effacer_impacts)

Il ne faut pas ajouter de parenthèses après le nom de la fonction. Avec command=effacer_impacts(), la fonction serait appelée immédiatement lors de la création du bouton.

Deux façons de déclencher une fonction
Interaction Association Paramètre reçu par la fonction
clic sur un Button command=fonction aucun
événement dans un widget widget.bind(evenement, fonction) un objet event

8. Organisation du programme final⚓︎

Exercice 10

Vérifier que le programme respecte l'organisation générale suivante.

🐍 Script Python
from tkinter import *

# Constantes
# ...

# Variables globales représentant l'état de l'application
# ...

# Fonctions
def dessiner_cible():
    pass

def calculer_points(x, y):
    pass

def tirer(event):
    pass

def effacer_impacts():
    pass


# Création et configuration de la fenêtre principale
fenetre = Tk()
# ...

# Création et placement des widgets
# ...

# Dessin initial et association des événements
dessiner_cible()
canvas.bind("<Button-1>", tirer)

# Boucle événementielle
fenetre.mainloop()
L'ordre reste important

Une fonction peut utiliser une variable globale créée plus loin dans le fichier, à condition que cette variable existe au moment où la fonction est réellement appelée.

En revanche, les fonctions doivent être définies avant les instructions qui les utilisent pour créer un bouton ou associer un événement.


9. Bilan⚓︎

Exercice 11

Répondre aux questions suivantes sans exécuter le programme.

Question 1

Quel widget constitue la fenêtre principale ?

Question 2

Quel widget permet de dessiner des formes géométriques ?

Question 3

Quelle méthode place simplement un widget dans une fenêtre ?

Question 4

Quel est le rôle de mainloop() ?

Question 5

Quelle différence y a-t-il entre command=effacer_impacts et canvas.bind("<Button-1>", tirer) ?

Question 5

Comment récupère-t-on les coordonnées d'un clic dans le canevas ?

Question 6

À quoi sert l'entier renvoyé par canvas.create_oval(...) ?

Question 7

Quelles sont les variables qui représentent l'état de l'application ? Quels objets appartiennent plutôt à son interface graphique ?