python tkinter

Identification

Infoforall

10 - Mini-projet Tkinter


Nous allons construire notre petite application graphique tkinter de façon progressive.

Cela vous permettra de vous familiariser avec la création d'interface graphique avec tkinter.

Nous continuerons à découvrir Tkinter au fil du temps dans d'autres autres mini-projets. Par contre, aucune connaissance attendue. L'utilisation d'une interface de ce type n'est pas au programme. L'intérêt est donc dans la gestion du code.

Logiciel nécessaire pour l'activité : Python 3, à travers Thonny ou autre.

1 - Première interface tkinter

Pourquoi mettre certaines variables en orange plutôt qu'en bleu ? Il s'agit des variables faisant référence à des objets propres à l'interface graphique, les widgets : mélange de window (fenêtre) et gadget. Cela vous permettra de voir clairement où se trouvent les parties du code propre à tkinter.

Pour commencer, voici une première partie du code : il permet de

  • créer une application graphique fenetre avec le module tkinter,
  • d'y insérer un canvas zone_mouvement (une zone dans laquelle on peut dessiner) et
  • de dessiner dans le canvas un cercle jaune identifié par mobile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - - # - - - - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin xo = 20 yo = 50 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - - - # Programme principal - - - - - - - - # - - - - - - - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre fenetre.mainloop() # Surveillance des événements sur la fenêtre

01° Utiliser le programme pour vérifier qu'il crée bien une application graphique comportant un point jaune.

Quelles sont les 5 variables globales du programme ? Parmi celles-ci, quelles sont les variables propres à tkinter ?

...CORRECTION...

  • xo contient la coordonnée initiale sur l'axe x
  • yo contient la coordonnée initiale sur l'axe y
  • fenetre contient la référence de la fenêtre graphique de l'application.
  • zone_mouvement contient la référence de la zone dans laquelle le dessin va bouger.
  • mobile contient la référence du dessin dans zone_mouvement.
Importation des codes contenus dans un module

Explication des lignes 4 et 15 :

  • import tkinter as tk
  • On importe le module avec import tkinter
  • Comme le nom tkinter est long à taper, on lui donne un alias avec as tk.
  • De cette façon, on peut créer l'application graphique stockée dans fenetre en utilisant la classe Tk du module tkinter
    • en tapant fenetre = tk.Tk()
    • plutôt que fenetre = tkinter.Tk().

02° Regarder les lignes 15 et 18. Donner le nom des deux fonctions-constructeurs qu'on part récupérer dans le module tk.

...CORRECTION...

On trouve deux lignes sur lesquelles on va chercher quelque chose dans le module :

  • fenetre = tk.Tk()
  • zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300)

Nous partons donc récupérer deux fonctions nommées Tk et Canvas.

03° Après avoir observé attentivement les noms de ces deux fonctions, quelle est la particularité qu'ils ont en commun ? Quelle est la règle de nommage que je vous impose et qui semble ne pas avoir été respectée ? Savez-vous pourquoi ?

...CORRECTION...

Premier constat : la couleur sur ce site ! Elles ne sont pas en rouge (pour les fonctions créées par l'utilisateur) ou en vert (pour les fonctions natives). On a un cyan, mélange de vert et de bleu.

Deuxième constat : le nom commence par une MAJUSCULE !

C'est parce qu'il s'agit de fonctions très particulières : ce sont des constructeurs d'objets.

Un objet est une entité plus complexe qu'un integer ou une chaîne de caractères. Il s'agit d'une construction qui contient

  • d'autres variables : des entiers, des strings ect . . . et
  • même d'autres fonctions !
Classe et objet

Pour construire un nouvel objet, on a besoin de connaitre sa structure de base, son moule.

Ce moule se nomme la Classe.

Par convention, le nom d'une classe commence TOUJOURS par une majuscule.

Pour simplifier la création de nouveaux objets, le constructeur possède par défaut le nom de la Classe. Comme il s'agit d'une fonction, on rajoute en plus les parenthèses.

Ainsi,

  • le widget fenetre est basé sur la classe Tk.
  • La fonction-constructeur se nomme Tk().

04° Vérifier que les variables soient en mémoire. Sinon, relancez le programme et fermez l'application.

Lancer les instructions suivantes dans le Shell :

>>> type(fenetre) <class 'tkinter.Tk'> >>> type(zone_mouvement) <class 'tkinter.Canvas'> >>> type(mobile) <class 'int'> >>> type(mobile) 1

Répondre à la question suivante : La variable mobile est-elle un objet ou un simple identifiant ?

...CORRECTION...

On voit donc que :

  • fenetre est un objet basé sur la classe Tk.
  • zone_mouvement est un objet basé sur la classe Canvas.
  • mobile est un simple integer contenant 1. Grace à ce numéro, on pourra retrouver le cercle : il est le dessin n°1 qu'on a tracé dans le Canvas.

Dans tkinter, les choses qu'on dessine dans un canvas sont simplement référencées par un numéro. La variable mobile contient donc simplement le numéro du dessin (1 ici).

Il nous reste à voir une dernière chose avant de parvenir à comprendre tout ce code : la notion de méthode. Regardons les lignes 21, 27 et 28 mises en évidence dans le code de base :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - - # - - - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin xo = 20 yo = 50 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - - - # Programme principal - - - - - - - - # - - - - - - - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre fenetre.mainloop() # Surveillance des événements sur la fenêtre

05° Les fonctions utilisées (create_oval, pack, mainloop) sont-elles issues d'un module ? Dans quoi semblent-elles être contenues ?

...CORRECTION...

Ces fonctions sont contenues dans les objets zone_mouvement et fenetre puisqu'on trouve leurs noms suivis d'un point suivis du nom de la fonction.

Ainsi le code zone_mouvement.create_oval(...) veut dire :

Va chercher dans zone_mouvement la fonction create_oval.

On retrouve bien la notion qu'un objet peut contenir des fonctions !

06° Vérifier que le module tk soit bien présent dans l'onglet Variable de Thonny. Sinon, lancer le programme pour le placer en mémoire. Utiliser la documentation pour trouver des informations sur l'utilité de l'une de ces fonctions :

>>> help(tk.Canvas.create_oval)

...CORRECTION...

Help on function create_oval in module tkinter: create_oval(self, *args, **kw) Create oval with coordinates x1,y1,x2,y2.

On voit donc qu'on crée une forme ovale entre les coordonnées de deux points M1 et M2.

Ce qui n'est pas noté directement dans la documentation de la fonction :

  • Le point M1(x1, y1) est en haut à gauche.
  • Le point M2(x2,y2) est en bas à droite.

Attention, contrairement au système d'axes Oxy classique en mathématiques, y est orienté vers le bas et le point d'origine (0,0) correspond au point en haut à gauche. C'est le standard en informatique.

systeme de coordonnees

Le module turtle est une exception : il fonctionne différemment car c'est un module d'initiation : le (0,0) est au centre d'écran, et l'axe Y est bien orienté vers le haut.

07° Donner les coordonnées des points M1 et M2 pour le cercle identifié par mobile en étudiant les lignes 11, 12 et 21.

Pour rappel, voilà la documentation de la méthode

Help on function create_oval in module tkinter: create_oval(self, *args, **kw) Create oval with coordinates x1,y1,x2,y2.

...CORRECTION...

Comme nous avions obtenu :

create_oval(self, *args, **kw) Create oval with coordinates x1,y1,x2,y2.
et que l'appel de la ligne 21 est :
mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow")

Après évaluation des expressions, cela revient à taper ceci :

mobile = zone_mouvement.create_oval(20, 50, 40, 70, width=1, fill="yellow")

On peut en déduire que :

  • Les coordonnées du point M1 sont :
    • x1 = 20
    • y1 = 50
  • Les coordonnées du point M2 sont :
    • x2 = 40
    • y2 = 70

    En gros, on trace un cercle car la hauteur et la largeur sont de 20 pixels.

Finalement, il ne nous reste que deux lignes à expliquer :

27 28
zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre fenetre.mainloop() # Surveillance des événements sur la fenêtre

La fonction pack place le widget dans la fenêtre. Sans rentrer dans les détails, on peut voir ici qu'on transmet l'ordre d'afficher le Canvas à gauche (side=tk.LEFT) de la fenêtre.

La fonction mainloop donne l'ordre à l'interpréteur de surveiller les événenements se passant sur fenetre (clics de souris, appuis sur le clavier ...) Cette instruction est à placer systématiquement à la fin de votre programme.

Cette surveillance permet de passer en programmation événementielle tant que la fenêtre est ouverte : le programme boucle sur cette instruction, surveille les événements et réagit en fonction des actions qu'on lui impose. On sort temporairement de la logique purement séquentielle étudiée depuis le début.

2 - fonction ou méthode

Vocabulaire

Depuis le début, j'utilise le mot fonction de façon assez générique.

Sur ce site, les fonctions apparaissent en rouge (sauf les fonctions natives bien entendu).

Les constructeurs d'objets sont eux en cyan.

Vous n'avez pas du voir la différence pour l'instant mais regardons la couleur de quelques lignes de code du programme précédent :

21 27 28
mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre fenetre.mainloop() # Surveillance des événements sur la fenêtre

Et oui, c'est du rouge mais pas le même que celui des fonctions. Pourquoi rajouter une nouvelle couleur sur le site ? Simplement parce qu'il s'agit d'un cas particulier des fonctions : ce sont des fonctions intégrées directement aux objets lors de leur création. On nomme ces fonctions des méthodes.

Dans la suite des explications, on distinguera désormais fonctions et méthodes.

3 - Boutons dans tkinter

Il est temps de rendre notre application interactive.

Nous allons créer des boutons et définir les actions à faire lorsqu'on clique sur ces boutons.

Nous allons créer :

  • Une procédure creer_boutons qui va rajouter des boutons sur l'interface. Lors de la création des boutons, on leur donnera l'ordre d'activer la fonction droite lorsqu'on clique sur l'un d'entre eux : command=droite
  • def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=droite).pack(fill=tk.X)
  • La procédure droite qui permet de faire bouger le dessin identifié par mobile et appartenant au Canvas zone_mouvement
  • def droite(): '''Fait avancer le dessin identifié par mobile de dx pixels vers la droite''' zone_mouvement.move(mobile, 10, 0)

Remarque : fill=tk.X permet de dire qu'on veut que le bouton occupe toute la place encore disponible sur X, l'axe horizontal.

08° Quel est le nom de la Classe sur laquelle sont basés les boutons ? Quel est le nom de la fonction qui va être activée dans les 4 cas ?

...CORRECTION...

Il suffit de lire cette ligne :

tk.Button(fenetre, ...)

On voit que le constructeur est Button(...) , la Classe est donc nommée Button.

Lorsqu'on appuie sur les boutons, on voit qu'on active la fonction droite : command=droite

09° Je vous donne le lien vers la documentation sur move du Canvas tkinter.

move(item, dx, dy) [#] Moves matching items by an offset. item : Item specifier. dx : Horizontal offset. dy : Vertical offset.

Regarder cette documentation automatique de la méthode move pour comprendre pourquoi j'ai noté ceci pour faire bouger le dessin identifié par mobile vers la droite : move(mobile, 10, 0).

Question : de combien de pixels sur la droite le dessin de numéro mobile va-t-il se déplacer ? De combien de pixels vers le bas le dessin va-t-il se déplacer ?

...CORRECTION...

move(item, dx, dy) [#] Moves matching items by an offset. item : Item specifier. dx : Horizontal offset. dy : Vertical offset.

Si on comparer à ceci move(mobile, 10, 0), on voit la correspondance suivante :

  • item reçoit mobile
  • dx reçoit 10
  • dy reçoit 0

Le dessin va donc se déplacer de 10 pixels sur l'axe x (de 10 vers la droite donc) et de 0 pixel sur l'axe y (de 0 pixel vers le bas donc).

10° Tester le code fourni ci-dessous qui regroupe l'ancien code et qui crée les boutons en plus.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - 2 - variables globales - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin xo = 20 yo = 50 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=droite).pack(fill=tk.X) def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 10, 0) # - - - - - - - - - - - - - - - - - - # Programme principal - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface fenetre.mainloop() # Surveillance des événements sur la fenêtre

11° Pourquoi le mobile avance -t-il vers la droite à chaque fois qu'on appuie sur l'un des boutons  ?

...CORRECTION...

Il faut regarder le nom de la fonction derrière command=.

12° Créer les fonctions gauche, haut et bas de façon à déplacer le mobile de 10 pixels dans la bonne direction lorsqu'on appuie sur le bouton.

Pensez à modifier les commandes des boutons également !

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
# - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - # 2 - variables globales - - - # - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin xo = 20 yo = 50 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 10, 0) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -10, 0) def bas(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, 10) def haut(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, -10) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface fenetre.mainloop() # Surveillance des événements sur la fenêtre

13° Modifier les 4 fonctions de déplacement : nous ne voulons plus imposer 10 pixels mais lire le contenu d'une variable globale qu'on nommera deplacement. Initialiser cette variable avec différentes valeurs pour visualiser que le programme fonctionne correctement.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 10 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) def gauche(): '''Fait avancer le mobile de 10 pixels vers la gauche''' zone_mouvement.move(mobile, -deplacement, 0) def bas(): '''Fait avancer le mobile de 10 pixels vers le bas''' zone_mouvement.move(mobile, 0, deplacement) def haut(): '''Fait avancer le mobile de 10 pixels vers le haut''' zone_mouvement.move(mobile, 0, -deplacement) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface fenetre.mainloop() # Surveillance des événements sur la fenêtre

4 - Animation

Nous avons maintenant une interface interactive. Néanmoins, il faut encore appuyer sur les boutons de multiples fois pour faire avancer notre rond.

Récursivité

Nous allons voir qu'il existe un moyen simple de lancer une animation : la récursivité.

Sur une fonction, la recursivité s'exprime par le fait qu'une fonction s'appelle elle-même de multiples fois.

Vous aurez plus tard dans cette formation une activité complète traitant de ce thème. Il ne s'agit ici que d'une introduction en lien avec l'animation.

Attention : si vous ne donnez aucun moyen à une animation de s'arrêter d'elle-même (ce que nous allons faire ici dans un premier temps, c'est plutôt mal), la fonction va s'auto-activer à l'infini. On appelle cela une boucle infinie. Normalement, on permet toujours au code de s'arrêter au bout d'un moment.

Récursivité et animation avec tkinter : méthode AFTER

Les objets-fenêtres de classe Tk possèdent une méthode bien pratique nommée after.

Cette méthode permet programmer l'appel d'une fonction au bout d'un certain temps exprimé en millisecondes.

Si on veut lancer la fonction droite dans l'application graphique fenetre après un temps d'attente de 1s, il suffit donc de taper ceci :

fenetre.after(1000, droite)

En effet, il faut 1000 ms pour faire 1 s.

14° Modifier les 4 fonctions de déplacement : nous voudrions qu'elles s'auto-activent automatiquement au bout de 0.5 s après avoir été activée une première fois avec un bouton. Ainsi, il faudrait que la fonction droite active automatiquement droite après 500ms.

...CORRECTION...

Un bout de correction :

def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) fenetre.after(500, droite) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -deplacement, 0) fenetre.after(500, gauche)

5 - Gestion des positions

Il reste encore un problème : on peut sortir de l'écran !

Il va donc falloir agir sur les coordonnées du mobile et donc réussir à les récupérer.

Point 1 : calcul des nouvelles coordonnées

Nous allons devoir agir avec un test IF de façon, par exemple, à continuer le mouvement de l'autre côté de l'écran de 300 pixels sur 300 pixels. On veut donc que les coordonnées x et y du point M1 du mobile soient supérieures à 0 pixel mais inférieures à 300 pixels.

Si le mouvement finit à 320 pixels en x, on replace le mobile à 20 pixels.

Le plus simple pour ce calcul ? Le modulo !

if x > 300 : x = x % 300 elif x < 0 : x = x % 300 if y > 300 : y = y % 300 elif y < 0 : y = y % 300

Nous avons ici décidé de ne gérer les coordonnées qu'à partir du point M1 de façon à simplifier la création du code. Nous aurions pu obtenir un code plus solide en cherchant le centre du centre en utilisant les coordonnées des points 1 et 2. Par exemple

  • xC = (x + x2) // 2
  • yC = (y + y2) // 2

Point 2 : connaitre les coordonnées actuelles

La méthode coords permet de récupérer les coordonnées x et y du point M1 et les coordonnées x2 et y2 du point M2.

Dans le cas de notre mobile, il suffit donc d'écrire ceci pour récupérer x et y :

# x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 elif x < 0 : x = x % 300 if y > 300 : y = y % 300 elif y < 0 : y = y % 300

Point 3 : modifer les coordonnées du mobile

Il suffit d'utiliser la méthode coords en transmettant plus d'information : il suffit de rajouter les coordonnées des points M1 et M2 : coords(numero_du_dessin, x, y, x2, y2).

Et voilà le code final :

# x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) elif x < 0 : x = x % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) if y > 300 : y = y % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) elif y < 0 : y = y % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20)

15° Créer une fonction nommée recadrer qui intégre le code précédent et qui s'auto-activera toutes les 200 ms par exemple. Vous penserez bien entendu à activer une première fois cette fonction dans le programme principal. Vérifier que le mobile sort d'un côté pour arriver de l'autre côté.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 10 # Création de la fenêtre du logiciel fenetre = tk.Tk() # Création du Canvas, la zone de déplacement du dessin zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) # Création du dessin qu'on va déplacer sur le Canvas mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) fenetre.after(500, droite) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -deplacement, 0) fenetre.after(500, gauche) def bas(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, deplacement) fenetre.after(500, bas) def haut(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, -deplacement) fenetre.after(500, haut) def recadrer() : # x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) elif x < 0 : x = x % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) if y > 300 : y = y % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) elif y < 0 : y = y % 300 zone_mouvement.coords(mobile, x, y, x+20, y+20) fenetre.after(200, recadrer) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface recadrer() # Vérifier et modifier éventuellement la position du mobile fenetre.mainloop() # Surveillance des événements sur la fenêtre

Ce code est loin d'être parfait : vous n'avez pas encore vu certains outils qui nous permettront de le simplifier et de le rendre moins répétitif. Mais il est fonctionnel avec les quelques outils à disposition, c'est ce qu'on lui demande !

6 - Démarrer ou stopper l'animation

C'est bien tout cela, mais pas moyen d'arrêter l'animation une fois lancée. Nous allons utiliser une variable globale qui nous permettra de savoir si on désire poursuivre une animation ou s'il faut la stopper.

Nommons cette variable animFlag pour "animationFlag", le drapeau (flag en anglais) d'autorisation de l'animation.

Si la variable animFlag vaut True, on autorise l'animation à continuer, sinon on la stoppe.

# - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # drapeau(flag) d'autorisation d'animation animFlag = True # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 10 # Création de la fenêtre du logiciel, du Canvas et du dessin fenetre = tk.Tk() zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow")

16° Rajouter cette variable animFlag. Modifier les 4 fonctions de direction de façon à ce qu'elles s'auto-activent uniquement SI le drapeau est True.

17° Rajouter deux boutons dans votre application :

  • l'un des boutons (ON par exemple) devra simplement permettre d'activer la fonction mettre_ON qui place la variable globale animFlag à True.
  • l'un des boutons (OFF par exemple) devra simplement permettre d'activer la fonction mettre_OFF qui place la variable globale animFlag à False.

Rappel : il faut utiliser le mot-clé global pour parvenir à modifier le contenu d'une variable globale depuis une fonction.

Voici une correction possible :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # drapeau(flag) d'autorisation d'animation animFlag = True # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 10 # Création de la fenêtre du logiciel, du Canvas et du dessin fenetre = tk.Tk() zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="ON", command=mettre_ON).pack(fill=tk.X) tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) tk.Button(fenetre, text="OFF", command=mettre_OFF).pack(fill=tk.X) def mettre_ON() : global animFlag animFlag = True def mettre_OFF() : global animFlag animFlag = False def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) if animFlag == True : fenetre.after(500, droite) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -deplacement, 0) if animFlag == True : fenetre.after(500, gauche) def bas(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, deplacement) if animFlag == True : fenetre.after(500, bas) def haut(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, -deplacement) if animFlag == True : fenetre.after(500, haut) def recadrer() : # x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif x < 0 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) if y > 300 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif y < 0 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) fenetre.after(200, recadrer) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface recadrer() # Vérifier et modifier éventuellement la position du mobile fenetre.mainloop() # Surveillance des événements sur la fenêtre

7 - Rebond

18° Question ouverte : modifier les 4 fonctions de mouvement : il faut faire rebondir la balle sur les bords. Ainsi :

  • Dans droite : si la coordonnée x2 est strictement inférieure à 300, on active bien droite, sinon on activera plutôt gauche de façon récursive.
  • Dans gauche : idem pour ce côté en regardant sur la variable x est inférieure ou supérieure à 10 pixels.
  • Faire de même avec les deux autres fonctions

Correction possible :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # drapeau(flag) d'autorisation d'animation animFlag = True # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 10 # Création de la fenêtre du logiciel, du Canvas et du dessin fenetre = tk.Tk() zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="ON", command=mettre_ON).pack(fill=tk.X) tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) tk.Button(fenetre, text="OFF", command=mettre_OFF).pack(fill=tk.X) def mettre_ON() : global animFlag animFlag = True def mettre_OFF() : global animFlag animFlag = False def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if x2 < 300 : fenetre.after(500, droite) else : fenetre.after(500, gauche) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -deplacement, 0) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 10 : fenetre.after(500, gauche) else : fenetre.after(500, droite) def bas(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, deplacement) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if y2 < 300 : fenetre.after(500, bas) else : fenetre.after(500, haut) def haut(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, -deplacement) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if y > 10 : fenetre.after(500, haut) else : fenetre.after(500, bas) def recadrer() : # x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif x < 0 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) if y > 300 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif y < 0 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) fenetre.after(200, recadrer) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface recadrer() # Vérifier et modifier éventuellement la position du mobile fenetre.mainloop() # Surveillance des événements sur la fenêtre

8 - Fluidité

Cette partie n'est à réaliser que s'il vous reste un peu de temps.

Vous pourriez encore augmenter la fluidité, pour l'instant c'est très saccadé.

19° Rajouter une variable globale nommée tempo qui contient initialement 500 (ms). Modifier les fonctions de déplacement pour qu'elles fassent référence à cette variable plutôt qu'à une valeur de 500 en dur.

20° Diminuer la valeur de tempo et deplacement pour obtenir un mouvement plus régulier et moins saccadé.

Dernière proposition de solution :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
# - - - - - - - - - - - - - - - - - - - - - - # 1 - importation des modules nécessaires - - # - - - - - - - - - - - - - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - # 2 - variables globales - - - - - # - - - - - - - - - - - - - - - - - # drapeau(flag) d'autorisation d'animation animFlag = True # Coordonnées initiales x et y du dessin et déplacement xo = 20 yo = 50 deplacement = 1 tempo = 50 # Création de la fenêtre du logiciel, du Canvas et du dessin fenetre = tk.Tk() zone_mouvement = tk.Canvas(fenetre, bg="dark grey", height=300, width=300) mobile = zone_mouvement.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - - - - - - - - - - - - - - - - # 3 - déclarations fonctions - - # - - - - - - - - - - - - - - - - def creer_boutons(): '''Crée et place les boutons''' tk.Button(fenetre, text="ON", command=mettre_ON).pack(fill=tk.X) tk.Button(fenetre, text="Avance", command=droite).pack(fill=tk.X) tk.Button(fenetre, text="Recule", command=gauche).pack(fill=tk.X) tk.Button(fenetre, text="Descend", command=bas).pack(fill=tk.X) tk.Button(fenetre, text="Monte", command=haut).pack(fill=tk.X) tk.Button(fenetre, text="OFF", command=mettre_OFF).pack(fill=tk.X) def mettre_ON() : global animFlag animFlag = True def mettre_OFF() : global animFlag animFlag = False def droite(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, deplacement, 0) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if x2 < 300 : fenetre.after(tempo, droite) else : fenetre.after(tempo, gauche) def gauche(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, -deplacement, 0) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 10 : fenetre.after(tempo, gauche) else : fenetre.after(tempo, droite) def bas(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, deplacement) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if y2 < 300 : fenetre.after(tempo, bas) else : fenetre.after(tempo, haut) def haut(): '''Fait avancer le mobile de 10 pixels vers la droite''' zone_mouvement.move(mobile, 0, -deplacement) if animFlag == True : x, y, x2, y2 = zone_mouvement.coords(mobile) if y > 10 : fenetre.after(tempo, haut) else : fenetre.after(tempo, bas) def recadrer() : # x,y : coordonnées du point en haut à gauche du mobile de 20px sur 20px x, y, x2, y2 = zone_mouvement.coords(mobile) if x > 300 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif x < 0 : x = x % 300 zone_mouvement(mobile, x, y, x+20, y+20) if y > 300 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) elif y < 0 : y = y % 300 zone_mouvement(mobile, x, y, x+20, y+20) fenetre.after(200, recadrer) # - - - - - - - - - - - - # Programme principal - - # - - - - - - - - - - - - zone_mouvement.pack(side=tk.LEFT) # Placement à gauche du canvas sur la fenetre creer_boutons() # Créations et placements des boutons de l'interface recadrer() # Vérifier et modifier éventuellement la position du mobile fenetre.mainloop() # Surveillance des événements sur la fenêtre

Et bien voilà. Vous avez réalisé votre premier petit jeu. Nous pourrions aller encore plus loin mais vous avez certainement remarqué que beaucoup de parties du code se ressemblent fortement. Plus de 100 lignes pour faire simplement bouger une balle, c'est un peu beaucoup non ? Nous aurions pu les regrouper via des fonctions ou des expressions booléennes basées sur ET ou OU.

Si vous cherchez un peu plus d'informations sur la gestion d'interfaces graphiques avec tkinter, vous pouvez aller voir les fiches dans le menu FICHES de Python : FICHE 1 TKINTER

Activité publiée le 28 08 2019
Dernière modification : 05 12 2019
Auteur : ows. h.