python projet 2

Identification

Infoforall

28 - Projet avec tkinter


Cette activité vous montre comment travailler en groupe sur un travail de type Interface Homme Machine.

Nous allons revoir une nouvelle fois la notion d'IHM : ici, il ne s'agira plus de Turtle ou de la console mais de Tkinter.

Votre code va donc pouvoir être séparé en deux parties distinctes :

  • Votre code Python qui interagit avec l'IHM Tkinter
  • Votre code Python qui gère les données

Prérequis :

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

1 - Création de l'application graphique

RAPPEL

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 coorcodes
Rappel des notions vues dans l'article Tkinter

La FICHE sur Tkinter contient les explications sur les notions suivantes. Si cela ne vous dit vraiment plus rien, commencez par lire l'article en activant les programmes au fur et à mesure de façon à bien comprendre l'utilisation de ces différentes notions.

FICHE Tkinter

  1. Partie 1 : Créer la fenêtre
    • Constructeur Tk
    • Méthode geometry
    • Méthode title
    • Méthode configure pour modifier après création
    • Méthode mainloop pour lancer la surveillance des événements
  2. Partie 2 : Créer, modifier et lire les attributs des widgets
    • Constructeur Label pour créer un widget Texte
    • Méthode pack pour afficher et placer simplement le widget
    • Méthode place pour afficher et placer à des coordonnées précises
    • Méthode configure pour modifier après création
    • Méthode cget pour récupérer la valeur d'un attribut
  3. Partie 3 : Utiliser le gestionnaire d'événements
    • Méthode bind pour utiliser le gestionnaire d'événements
    • Description des différents événements
    • Fonction événementielle et paramètre event pour récupérer les informations de l'événement
  4. Partie 4 : Placer précisement les widgets
    • Méthode place pour afficher et placer le widget à des coordonnées précises
  5. Partie 5 : Animer automatiquement
    • Méthode after pour programmer un appel retardé à une fonction
  6. Partie 6 : Utilisation avancée
    • Fonction lambda pour envoyer l'adresse d'une fonction créée à la volée (hors programme, à n'utiliser que dans le cadre des interfaces graphiques)

Nous allons voir comment réaliser une fenêtre graphique dans laquelle nous allons placer nos éléments graphiques. Par exemple ceci :

Une application graphique avec des cases colorées

Nous prendrons comme support de travail le plateau de jeu que nous avions modélisé dans l'activité Matrice de la partie Données.

La grande différence par rapport aux codes précédents sur Tkinter ? Nous allons faire comme lors de l'activité sur le pendu : nous allons clairement séparer :

  1. la partie "Interface" : les variables et les fonctions qui dépendent de l'Interface Homme Machine (IHM) (l'interface graphique Tkinter ici)
  2. la partie "Données" : les variables et les fonctions qui contiennent les données brutes sur le plateau de jeu (tout ce qui n'aura pas à être modifié si on décide de passer de l'interface Tkinter à l'interface Qt ou autre par exemple)

Nous allons construire peu à peu une interface de ce type dans cette activité. L'activité suivante consiste à sa "transformation" de façon à en faire un véritable jeu. Commençons par la création de :

  • la fenêtre servant d'écran principal fenetre
  • la matrice donnees qui contient les données encodant les cases du plateau, sous forme d'un simple entier
  • d'un tableau complement contenant des informations complémentaires diverses sur le jeu.
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
# ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # ----- 3 - FONCTIONS liées à la partie DONNEES ---------------- # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, 0 par défaut donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

Que fait le programme ?

Ligne 3 : importation du module sous l'alias tk

3
import tkinter as tk

Lignes 14-23 : passons pour l'instant sur la partie déclaration des fonctions. Nous verrons ce qu'elles font lorsque nous aurons besoin de le savoir.

Lignes 31-32 dans le programme principal : on commence par l'utilisation des docstrings en tant que jeu de tests :

28 29 30 31 32
if __name__ == '__main__': # A - Activation des tests sur les fonctions du module import doctest doctest.testmod()

Lignes 36-47 : créations de 2 variables globales.

On commence avec un tableau complement contenant des données générales sur l'état du jeu et on rajoute une matrice donnees qui encode le contenu des cases.

36 37 38 39 40 41 42 43 44 45 46 47
# informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, 0 par défaut donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]
  • complement est un tableau ayant deux éléments :
    • Indice 0 : le numéro du joueur actif.
    • Indice 1 : le nombre de coups joués.
  • donnees est une matrice, un tableau de tableaux, qui va contenir les données liées aux cases du jeu. Dans la version présentée ici, on y stocke juste une valeur par case (par exemple 0 pour non sélectionnée ou pour dire mur, 1 pour sélectionnée par le joueur 1 ou pour dire couloir...), mais on pourrait y mettre un tuple, un string, un autre tableau... On accède au contenu d'une case précise à l'aide de sa ligne et sa colonne :
    donnees[ligne][colonne].

S'agissant de deux tableaux (qui sont des structures de données muables en Python), nous pourrons ainsi modifier le contenu depuis les fonctions simplement en leur transférant l'identifiant-mémoire.

01° Que renvoie donnees[2][4] ?

...CORRECTION...

On se souviendra que l'index commence à 0.

Le contenu de la case de ligne d'index 2.

15 16 17 18 19 20 21 22 23
# matrice encodant les données du plateau, 0 par défaut donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Il ne reste qu'à chercher la colonne d'index 4 : elle fait référence à la valeur 3.

15 16 17 18 19 20 21 22 23
# matrice encodant les données du plateau, 0 par défaut donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

02° Que renvoie len(donnees) ?

...CORRECTION...

On parvient à trouver le nombre de lignes en cherchant la longueur de donnees. Si vous regarder ci-dessous, vous pourrez constatez qu'on dénombre 6 éléments.

83 84 85 86 87 88 89 90
donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

03° Que renvoie len(donnees[0]) ou len(donnees[1]) ?

...CORRECTION...

On obtient le nombre de colonnes dans l'élément 0 qui est un tableau : on obtient donc le nombre de colonnes, on détermine la longueur du premier élément de donnees. Si vous comptez, vous trouverez 6.

83 84 85 86 87 88 89 90
donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Lignes 50-51 : création de la fenêtre graphique.

49 50 51
# --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre)

Ligne 50 : Création d'un objet de classe Tk et stockage de sa référence dans fenetre.

Ligne 51 : Activation de la procédure configurer_fenetre qui reçoit ici l'argument fenetre et le place dans son paramètre fe.

14 15 16 17 18 19 20 21 22 23
def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black")
  • Ligne 21 : geometry permet de fournir les dimensions en pixels.
  • Ligne 22 : title permet de faire de même pour le titre de la fenêtre
  • Ligne 23 : configure permet de modifier la couleur de fond (bg) à qui on affecte la couleur "black"

04° Lancer l'interface pour visualiser ce que cela donne. Modifier quelques valeurs de la fonction configurer_fenetre pour voir le résultat.

On rappelle que les couleurs d'une image sont définies en R-V-B (ou RGB en anglais). On peut donc soit donner le nom d'une couleur prédéfinie ou fournir les valeurs RGB en hexadécimal. Chaque composante peut prendre une valeur allant de 00 à FF.

Exemples :

"#000000" équivalent à "black"

"#FF0000" équivalent à "red"

"#00FF00" équivalent à "green"

"#FFFF00" équivalent à "yellow"

A partir de là, on peut lancer l'interface graphique avec cette dernière partie :

Activation de la surveillance en boucle sur l'interface graphique jusqu'à fermeture

53 54
# --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

2 - Les carrés colorés

Voyons maintenant comment créer des carrés colorés sur cette interface. Ici :

  • le carré numéro 0 est aux coordonnées x = 0 et y = 0
  • le carré numéro 1 est aux coordonnées x = 1 et y = 0
  • le carré numéro 5 est aux coordonnées x = 1 et y = 1

Dans notre programme, x correspondra donc à l'indice de la colonne (il commence à 0) et y correspondra à l'indice de la ligne (il commence à 0 également).

05° Lancer le code ci-dessous pour voir la nouveauté qu'il apporte. Lire ensuite la suite des explications.

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
# ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # ----- 3 - FONCTIONS liées à la partie DONNEES ---------------- # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py) # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y # Option : modification du widget (juste pour montrer qu'on peut !!) if x == 1: # nous sommes sur la 2e colonne (indice 1) w[y][x].configure(bg='red') elif y == 2: # nous sommes sur la 3e ligne (indice 2) w[y][x].configure(bg='#AA0000') # On renvoie la matrice return w # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, 0 par défaut donnees = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

Création de la matrice de stockage des Labels

Ligne 97, on crée la nouvelle matrice nommée widgets qui contient les références des différents widgets colorés. Cette création utilise la fonction creer_cases. Voyons comment elle fonctionne.

L'appel à la fonction se fait ainsi :

96 97
# --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees)

Le début du code de la fonction est :

25 26 27 28 29 30 31 32 33 34 35 36 37 38
def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] ...

Ligne 97 + Ligne 25 : le paramètre fe reçoit donc la réference de la fenêtre fenetre et le paramètre d reçoit la matrice donnees.

Lignes 33 et 34 : on récupère le nombre de lignes nbl et le nombre de colonnes nbc de la matrice donnees (voir Q02 et Q03).

Ligne 37 : cette dernière ligne permet de voir qu'on crée une matrice puisqu'on crée un tableau de tableaux.

37
w = [ [None for x in range(nbc)] for y in range(nbl) ]

06° QCM : Cette matrice w (pour dire widgets représentant graphiquement les cases du plateau) est construite par :

  • A : Compréhension
  • B : Déception
  • C : Incompréhension
  • D : Extension et ajouts successifs

...CORRECTION...

  • A : Compréhension
  • Création des cases et remplissage de cases

    Nous avons donc une belle matrice vide nommée w. Voyons comment nous parvenons à la remplir.

    Lignes 40-41 : double boucle permettant d'accéder aux coordonnées des cases de la matrice dans l'ordre habituel de lecture : de gauche à droite et avec un passage à la ligne suivante :

    .. 39 40 41 42 43 44 45 46 47 ..
    ... # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) ...

    On va donc boucler plusieurs fois et obtenir la suite de valeurs suivantes pour ligne_y et colonne_x :

    Si on prend l'exemple d'une petite matrice 3x3 avec des indices valant donc 0, 1 ou 2, les indices successifs pris par les variables de boucle y et x sont donc :
    (y=0, x=0) puis (y=0, x=1) puis (y=0, x=2) puis
    (y=1, x=0) puis (y=1, x=1) puis (y=1, x=2) puis
    (y=2, x=0) puis (y=2, x=1) puis (y=2, x=2) et c'est fini.

    07° QCM : Cela revient donc à parcourir les cases du plateau :

    • A : En commençant en haut à gauche et en partant d'abord vers la droite
    • B : En commençant en haut à gauche et en partant d'abord vers le bas
    • C : En commençant en bas à gauche et en partant d'abord à droite
    • D : En commençant en bas à gauce et en partant d'abord en haut

    ...CORRECTION...

  • A : En commençant en haut à gauche et en partant d'abord vers la droite
  • Bon, en même temps, la question n'était pas difficile puisque les cases sont numérotées de base dans l'ordre de création !

    Reprenons la création du Label en ligne 47 :

    47
    w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5)

      Nous avons donc création d'un objet widget de class Label. On lui transmet différents paramètres nommés, dont on peut trouver la description dans la documentation de Tkinter :

      • Un paramètre obligatoire : la réference d'une fenêtre Tkinter qui contiendra ce Label : fe ici
      • text, le texte qu'on veut voir s'afficher dans le widget Label
      • fg pour foreground, fond du premier plan, le texte.
      • bg pour background, fond coloré.
      • width pour width, la largeur (en largeur de caractères puisqu'il s'agit d'un texte)
      • height pour height, la hauteur (en hauteur de caractères puisqu'il s'agit d'un texte).

    08° Répondre aux deux questions en analysant la ligne 47.

    • Quel est le texte qu'on veut voir s'afficher dans notre carré ?
    • Quelle devrait être la couleur de fond de tous nos carrés ?

    ...CORRECTION...

    On veut afficher le numéro de la case : text=numero

    Toutes les cases devraient être grises : bg='grey'

    Voyons maintenant comment on parvient à placer nos widgets colorés. Pour l'instant, le widget-case est créé et mémorisé dans la matrice w mais tant qu'on n'a pas utilisé une méthode de placement, le widget ne s'affichera pas à l'écran.

    49 50 51 52
    # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py)

    Si les écartements entre cases ne vous conviennent pas, il suffit de modifier les valeurs.

    On rajoute ensuite quelques informations liées à la position de la case qui vont être assez utiles faire le lien entre la matrice widgets et la matrice donnees : son numéro de case, son numéro de ligne et de colonne :

    54 55 56 57
    # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y

    Ainsi si on veut récupérer le numéro de ligne d'un Label sur lequel on vient de cliquer et dont la référence est stockée dans case, il suffira de taper ceci : case.ligne. Pratique, non ?

    On trouve enfin une modification des caractéristiques de la case. Nous aurions pu les placer directement lors de la création : elles ne servent qu'à vous montrer comment modifier avec configure les attributs d'un Label qui existe déjà.

    59 60 61 62 63
    # Option : modification du widget (juste pour montrer qu'on peut !!) if x == 1: # nous sommes sur la 2e colonne (indice 1) w[y][x].configure(bg='red') elif y == 2: # nous sommes sur la 3e ligne (indice 2) w[y][x].configure(bg='#AA0000')

    Comme nous l'avons vu, les carrés devraient être gris. Or, ils ne sont pas tous gris. Pourquoi ?

    • Ligne 60 : on teste si notre case se situe sur la colonne d'indice 1
    • Ligne 61 : si c'est le cas, on utilise la méthode configure qui modifie le fond coloré (bg) en rouge vif (red).
    • Ligne 62 : sinon, on teste sur notre case se situe sur la ligne d'indice 2
    • Ligne 63 : si c'est le cas, on utilise la méthode configure pour modifier le fond coloré en rouge foncé.

    09° Modifier le script pour afficher le texte 'C1' dans les carrés de la colonne 1.

    ...CORRECTION...

    59 60 61 62 63 64
    # Option : modification du widget (juste pour montrer qu'on peut !!) if x == 1: # nous sommes sur la 2e colonne (indice 1) w[y][x].configure(bg='red') w[y][x].configure(text='C1') elif y == 2: # nous sommes sur la 3e ligne (indice 2) w[y][x].configure(bg='#AA0000')

    10° Pourquoi la case de colonne 1 et de ligne 2 a-t-elle la couleur imposée par sa colonne alors que le test de la ligne est vrai 

    59 60 61 62 63
    # Option : modification du widget (juste pour montrer qu'on peut !!) if x == 1: # nous sommes sur la 2e colonne (indice 1) w[y][x].configure(bg='red') elif y == 2: # nous sommes sur la 3e ligne (indice 2) w[y][x].configure(bg='#AA0000')

    ...CORRECTION...

    Justement parce que le test de la ligne se fera APRES celui de la colonne et SI le bloc de la colonne n'a pas été activé : dans une séquence IF-ELIF-ELSE, on n'effectue que le premier bloc détécté comme valide.

    Et une fois que nous sommes sortis des deux boucles FOR et avons stocké tous les labels dans la nouvelle matrice, il faut la renvoyer puisqu'il s'agit d'une variable locale : si on ne la renvoie pas, elle disparaitrait dès qu'on sort de cette fonction.

    65 66
    # On renvoie la matrice return w
    Faciliter la lecture du code

    Les variables globales n'ont pas vocation à être directement lues ou modifiées depuis une fonction si on peut faire autrement.

    Les transmettre en arguments et les placer dans des paramètres est la meilleur possibilité. Par contre, changer le nom des paramètres à chaque fois est ennuyeux pour la compréhension du code.

    Vous remarquerez que j'ai fait le choix que garder constamment les mêmes noms pour les paramètres contenant des références vers des variables globales.

    1. fe dans le paramètre de fonction qui doit recevoir la variable globale fenetre
    2. d comme variable locale associée à la variable globale donnees : il s'agit de la matrice contenant les données encodant le contenu des cases.
    3. w comme variable locale associée à la variable globale widgets : il s'agit de la matrice contenant les widgets représentant graphiquement les cases.
    4. c comme variable locale associée à la variable globale complement : il s'agit du tableau contenant les informations complémentaires sur le jeu.

    On pourrait leur donner exactement le même nom mais on évitera : celui permet de créer momins de confusion entre variable globale et variable locale.

    Variable d'une seule lettre ?

    Attention aux variables d'une seule lettre.

    Pratiques

    • car elles allègent la lecture.

    Dangereuses

    • car on aura vite fait de créer une autre variable portant le même nom et écrasant l'ancien contenu.
    • car elles ne sont pas vraiment explicites.

    3 - Partie Données vers partie Interface graphique

    Constantes

    Nous allons utilisé ici des CONSTANTES, des "variables" dont le contenu ne sera pas variable mais constant : on affecte un contenu au début et ensuite il ne changera jamais lors du déroulement du programme.

    Par convention, on utilise uniquement des majuscules dans le nom des constantes. Elles indiquent clairement qu'il ne faudra pas les modifier ensuite.

    En Python, elles n'existent pas vraiment : nos constantes ne sont en réalité que des variables dont le nom est composé de majuscules. Rien ne vous empêche de modifier le contenu après initialisation du contenu. Il existe par contre des langages qui distinguent variables et constantes et empêchent les modifications ultérieures.

    Dans nos programmes, nous nous autoriserons à LIRE les valeurs des constantes globales depuis les fonctions plutôt que de les transférer via des paramètres.

    Pour l'instant l'affichage n'a aucun rapport avec ce qu'on a placé dans la matrice donnees. Nous allons ici nous intéresser à la liaison DONNEES vers INTERFACE : on veut que les données stockées dans la matrice donnees soient visibles à l'écran. Il faudrait ainsi que les 0, 1, 3 et 8 stockés dans donnees soient transformés en effet visible à l'écran. Voici le début du nouveau code.

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    # ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # Codification des contenus COULOIR = 0 MUR = 1 AVENTURIER = 3 DRAGON = 8 # texte à afficher - couleur - couleur au survol APPARENCE = { COULOIR: ('', '#EEEEEE', '#DDDDDD'), MUR: ('', 'black', '#111111'), AVENTURIER: ('A', '#8888FF', '#AAAAFF'), DRAGON: ('D', '#FF5555', '#FF7777') }

    11° Mettre ce code en mémoire : il place en mémoire les 5 constantes, dont APPARENCE qui est une CONSTANTE-DICTIONNAIRE.

    Lancer ensuite ces évaluations dans la console.

    Questions

    1. Quel est le type des clés du dictionnaire ?
    2. Quel est l'intérêt de taper DRAGON plutôt que juste 8 ?
    3. Quel est le type de valeurs du dictionnaire ?
    4. Que va renvoyer la dernière évaluation ?
    >>> DRAGON 8 >>> APPARENCE[DRAGON] ('D', '#FF5555', '#FF7777') >>> APPARENCE[8] ('D', '#FF5555', '#FF7777') >>> APPARENCE[AVENTURIER][1]

    ...CORRECTION...

    1. Les clés sont des integers puisque les constantes MUR, COULOIR... sont des integers.
    2. Utiliser les constantes plutôt que les entiers directement permet de centraliser les valeurs des entiers. Si on décide de changer et de mettre 7 pour DRAGON, il suffit de modifier la ligne 12 et pas TOUTES les lignes où le 8 apparaît.
    3. Les valeurs du dictionnaire sont toutes des puplets (tuples) de 3 valeurs. Des triplets.
    4. Lorsqu'on tape APPARENCE[AVENTURIER], on récupère le triplet ('A', '#8888FF', '#AAAAFF')
    5. Lorsqu'on tape APPARENCE[AVENTURIER][1], on récupère donc l'élément d'indice 1 du triplet ('A', '#8888FF', '#AAAAFF') et donc '#8888FF'

    Nous utiliserons aussi les constantes dans la matrice donnees. Mais comme les noms des constantes sont longs, nous allons utiliser des alias :

    1 2 3 4 5 6 7 8 9 10 11 12 13
    # matrice encodant les données du plateau, C par défaut C = COULOIR M = MUR A = AVENTURIER D = DRAGON donnees = [ [M, C, C, M, M, C], [M, C, D, M, M, C], [M, C, C, C, D, C], [A, C, C, C, M, M], [M, C, M, C, M, C], [M, C, M, C, C, C] ]

    Nous voudrions maintenant créer la fonction modifier_apparence_cases. Cette fonction devra

    1. regarder chaque valeur contenue dans donnees et
    2. modifier en conséquence l'apparence du widget correspondant contenu dans widgets.

    Comme il s'agit de modifier l'interface à partri des données, il s'agit bien d'une fonction à placer dans la partie Interface. Nous allons partir du principe "une tâche - une fonction":

    • modifier_apparence_case se charge de modifier l'apparence d'un widget en fonction d'une valeur de contenu qu'on lui transmet (0 ou MUR par exemple).
    • modifier_apparence_cases se charge de le faire sur toutes les cases, en utilisant modifier_apparence_case.

    12° Mettre ce code en mémoire pour vérifier qu'il fonctionne sur votre système.

    Vous devriez obtenir ceci :

    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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    # ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # Codification des contenus COULOIR = 0 MUR = 1 AVENTURIER = 3 DRAGON = 8 # texte à afficher - couleur - couleur au survol APPARENCE = { COULOIR: ('', '#EEEEEE', '#DDDDDD'), MUR: ('', 'black', '#111111'), AVENTURIER: ('A', '#8888FF', '#AAAAFF'), DRAGON: ('D', '#FF5555', '#FF7777') } # ----- 3 - FONCTIONS liées à la partie DONNEES ------------- # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py) # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y # Option : modification du widget (juste pour montrer qu'on peut !!) #if x == 1 : # nous sommes sur la 2e colonne (indice 1) # w[y][x].configure(bg='red') #elif y == 2 : # nous sommes sur la 3e ligne (indice 2) # w[y][x].configure(bg='#AA0000') # On renvoie la matrice return w def modifier_apparence_case(case, v): '''Modifie l'apparence du widget en fonction de la valeur v :: param case (tkinter.Label) :: la référence du widget-case à modifier :: param v (int) :: le code du contenu à afficher dans la case :: return (None) :: "procédure" Python .. effet de bord :: modifie le widget case ''' if v in APPARENCE.keys(): case.configure(text=APPARENCE[v][0]) case.configure(bg=APPARENCE[v][1]) def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets modifier_apparence_case(w[5][0], AVENTURIER) modifier_apparence_case(w[0][0], d[0][0]) # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, C par défaut C = COULOIR M = MUR A = AVENTURIER D = DRAGON donnees = [ [M, C, C, M, M, C], [M, C, D, M, M, C], [M, C, C, C, D, C], [A, C, C, C, M, M], [M, C, M, C, M, C], [M, C, M, C, C, C] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees) modifier_apparence_cases(widgets, donnees) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

    13° L'appel à la fonction modifier_apparence_cases avec un s se fait ligne 146 :

    146
    modifier_apparence_cases(widgets, donnees)

    Et voici la fonction modifier_apparence_cases pour le moment :

    95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets modifier_apparence_case(w[5][0], AVENTURIER) modifier_apparence_case(w[0][0], d[0][0])

    Questions

    1. Dans quel paramètre est stocké l'argument widgets envoyé en ligne 146 ?
    2. Dans quel paramètre est stocké l'argument donnees envoyé en ligne 146 ?
    3. Sur quelle ligne de la fonction modifier_apparence_cases demande-t-on de modifier en l'apparence du widget ligne 5 et colonne 0 ?
    4. Sur quelle ligne de la fonction modifier_apparence_cases demande-t-on de modifier en l'apparence du widget ligne 5 et colonne 0 ?
    5. Sur quelle ligne de la fonction modifier_apparence_cases demande-t-on de modifier en l'apparence du widget ligne 0 et colonne 0 ?
    6. Que renvoie l'évaluation d'AVENTURIER ?
    7. Que renvoie l'évaluation de d[0][0] ?

    ...CORRECTION...

    1. Dans quel paramètre est stocké l'argument widgets envoyé en ligne 146 ?
    2. Dans le premier paramètre, celui qui se nomme w

    3. Dans quel paramètre est stocké l'argument donnees envoyé en ligne 146 ?
    4. Dans le deuxième paramètre, celui qui se nomme d

    5. Sur quelle ligne de la fonction modifier_apparence_cases demande-t-on de modifier en l'apparence du widget ligne 5 et colonne 0 ?
    6. 109
      modifier_apparence_case(w[5][0], AVENTURIER)
    7. Sur quelle ligne de la fonction modifier_apparence_cases demande-t-on de modifier en l'apparence du widget ligne 0 et colonne 0 ?
    8. 110
      modifier_apparence_case(w[0][0], d[0][0])
    9. Quelle valeur est obtenue par l'évaluation d'AVENTURIER ?
    10. AVENTURIER rest évaluée à 8.

    11. Que valeur est obtenue par l'évaluation de d[0][0] ?
    12. d[0][0] veut dire d'aller voir la ligne 0 puis la colonne 0 dans la matrice d qui est un alias pour la matrice globale donnees. On y trouve M qui vaut 1. d[0][0] est donc évalué à 1.

    14° Regardons maintenant pourquoi les deux cases s'affichent pour l'une en noir et pour l'autre en bleu ciel.

    Voici les deux appels à modifier_apparence_case, sans s :

    109 110
    modifier_apparence_case(w[5][0], AVENTURIER) modifier_apparence_case(w[0][0], d[0][0])

    Et voici le code de la fonction modifier_apparence_case :

    82 83 84 85 86 87 88 89 90 91 92 93
    def modifier_apparence_case(case, v): '''Modifie l'apparence du widget en fonction de la valeur v :: param case (tkinter.Label) :: la référence du widget-case à modifier :: param v (int) :: le code du contenu à afficher dans la case :: return (None) :: "procédure" Python .. effet de bord :: modifie le widget case ''' if v in APPARENCE.keys(): case.configure(text=APPARENCE[v][0]) case.configure(bg=APPARENCE[v][1])

    Question

    1. Lors de l'appel ligne 109, quel est le widget reçu dans le paramètre case : celui de la ligne 0 du haut ou celui de la ligne 5 du bas ?
    2. Lors de l'appel ligne 109, quelle est la valeur associée au paramètre v ?
    3. Que contient le tableau obtenu lorsqu'on utilise APPARENCE.keys ?
    4. Que veut dire en français if v in APPARENCE.keys ?
    5. Expliquer alors les modifications faites au widget lors de l'appel ligne 109.
    6. Expliquer alors les modifications faites au widget lors de l'appel ligne 110.

    ...CORRECTION...

    1. Lors de l'appel ligne 109, quel est le widget reçu dans le paramètre case : celui de la ligne 0 du haut ou celui de la ligne 5 du bas ?
    2. Celui en bas à gauche. C'est celui qui est bleu ciel.

    3. Lors de l'appel ligne 109, quelle est la valeur associée au paramètre v ?
    4. 8 puisqu'on transmet AVENTURIER.

    5. Que contient le tableau obtenu lorsqu'on utilise APPARENCE.keys ?
    6. On obtient un tableau qui contient toutes les clés disponibles dans le dictionnaire.

      [0, 1, 3, 8].

    7. Que veut dire en français if v in APPARENCE.keys ?
    8. On teste si la valeur v reçue (8) est bien l'une des clés du dictionnaire-constant APPARENCE. Puisque c'est vrai ici, le test est validé et on effectuera les actions visibles sur les lignes 92 et 93.

    9. Expliquer alors les modifications faites au widget lors de l'appel ligne 109.
    10. Ligne 92 : on modifie le texte de ce widget par le texte présent à l'indice 0 de la valeur associée à la clé 8. On y trouve un 'A'.

      Ligne 93 : on modifie la couleur de fond de ce widget par la couleur présente à l'indice 1 de la valeur associée à la clé 8. On y trouve un '#8888FF' : Rouge et Vert à 88 et le bleu à fond sur FF. On obtient donc du bleu ciel.

    11. Expliquer alors les modifications faites au widget lors de l'appel ligne 110.
    12. L'appel montre qu'on envoie le widget présent en ligne 0 et colonne 0 et que la valeur associé est celle de d[0][0] qui se trouve être 1 (voir la question précédente).

      Ligne 92 : on modifie le texte de ce widget par le texte présent à l'indice 0 de la valeur associée à la clé 1. On y trouve un ''.

      Ligne 93 : on modifie la couleur de fond de ce widget par la couleur présente à l'indice 1 de la valeur associée à la clé 1. On y trouve un 'black'.

    15° Modifier maintenant la fonction modifier_apparence_cases pour qu'elle fasse ce qu'on lui demande en réalité : lire la matrice d case par case et imposer la bonne apparence aux widgets stockés dans la matrice w.

    Il faudra bien entendu utiliser deux boucles imbriquées. Vous noterez que j'ai déjà récupéré le nombre de lignes et le nombre de colonnes.

    Résultat attendu de ce type :

    ...CORRECTION...

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets for y in range(nbl): for x in range(nbc): modifier_apparence_case(w[y][x], d[y][x])

    Vous savez maintenant créer une interface (vous avez au moins un exemple)

    Et vous savez maintenant comment afficher les données vers la partie Interface puis vers l'IHM de façon à informer l'utilisateur.

    Prochaine partie : récupérer les choix de l'utilisateur via l'IHM, les transmettre à votre partie Interface et transmettre ces choix à la partie Données.

    Nous allons donc retrouver une gestion des événements.

    4 - Evénements

    Tout d'abord : qu'est-ce qu'un événement pour l'interface ? Il s'agit d'une modification de l'environnement extérieur : une touche de clavier sur laquelle on appuie, le déplacement de la souris, l'appui sur une touche de la souris...

    Rappel de l'article Tkinter : gestionnaire d'événement et fonction-événement Tkinter

    C'est ce gestionnaire qui va donner l'ordre d'activer une fonction particulière si cet événement survient.

    Pour faire la liaison événement à surveiller ↦ fonction à réaliser, on utilise la méthode bind, appliquée au widget qu'on surveille.

    Codification de la liaison avec bind

    • On active sur le widget voulu (fenetre ici, mais c'est souvent un label directement)
    • la surveillance d'une utilisation de la flèche Gauche, identifiée par '<KeyPress-Left>'
    • qui provoque l'appel à la fonction nommée evt_fleche_g
      ...
      fenetre.bind("<KeyPress-Left>", evt_fleche_g) # Lorsqu'on appuie sur la flèche Gauche

    Fonction événementielle

    La fonction evt_fleche_g est une fonction normale si ce n'est que :

    1. c'est le gestionnaire d'événements qui la lance lorsqu'il détecte que l'événement est présent.
    2. elle doit avoir un premier paramètre (souvent nommé event) permettant d'accueillir l'événement.

    Que peut faire cette fonction ?

    Ce qu'on veut. C'est d'ailleurs pour cela qu'on a le fameux paramètre event. On peut notamment récupérer l'identifiant du widget ayant déclenché l'événement en tapant event.widget.

    ... ... ... ...
    def evt_fleche_g(event): reference_widget = event.widget print("Vous venez d'appuyer sur la flèche de gauche") reference_widget.configure(bg='#440000')

    Rien d'original : lorsqu'on détecte l'appui sur la FLECHE GAUCHE du clavier alors que reference_widget est actif, on affiche un petit message dans la console et on modifie la couleur du fond du widget.

    On notera qu'on ne place par les parenthèses de la fonction : on ne donne que son adresse interne. Sous cette forme, on ne peut donc pas fournir d'arguments.

    Le transfert d'arguments est possible mais à travers quelques notions que nous n'aborderons pas ici. Voir FAQ si vous voulez plus de détails.

    Liste d'événements

    Pas de blabla. Juste la description de l'événement et la façon de le gérer.

    Evénements liés à la souris

    1 2 3 4 5 6 7 8 9 10 11
    ref_widget.bind('<Button-1>', agir1) # Appui sur clic-gauche sur ce widget ref_widget.bind('<Button-2>', agir2) # Appui sur clic-molette sur ce widget ref_widget.bind('<Button-3>', agir3) # Appui sur clic-droit sur ce widget ref_widget.bind('<ButtonRelease-1>', agir4) # Relachement après clic-gauche ref_widget.bind('<ButtonRelease-2>', agir5) # Relachement après clic-molette ref_widget.bind('<ButtonRelease-3>', agir6) # Relachement après clic-droit ref_widget.bind('<Enter>', agir7) # Pointeur souris rentre sur ce widget ref_widget.bind('<Leave>', agir8) # Pointeur souris sort de ce widget ref_widget.bind('<Motion>', agir9) # Pointeur souris sort de ce widget

    Si vous voulez que la détection soit appliquée sur toute votre fenêtre d'application, il faut lier l'événement à votre fenêtre principale (fenetre dans le code de l'activité présentée ici).

    Evénements liés aux touches du clavier

    On notera qu'il faut cette fois lié l'événement à un widget-fenêtre. C'est par exemple le widget fenetre de notre activité.

    Voici pour les flèches.

    1 2 3 4 5 6 7 8 9
    fenetre.bind('<KeyPress-Left>', agir1) # Appui sur la flèche Gauche fenetre.bind('<KeyPress-Right>', agir2) # Appui sur la flèche Droit fenetre.bind('<KeyPress-Up>', agir3) # Appui sur la flèche Haut fenetre.bind('<KeyPress-Down>', agir4) # Appui sur la flèche Bas fenetre.bind('<KeyRelease-Left>', agir5) # Relachement de la flèche Gauche fenetre.bind('<KeyRelease-Left>', agir6) # Relachement de flèche Droit fenetre.bind('<KeyRelease-Up>', agir7) # Relachement de flèche Haut fenetre.bind('<KeyRelesase-Down>', agir8) # Relachement de la flèche Bas

    On peut aussi demander à agir si n'importe quelle touche est activée :

    1
    fenetre.bind('<Any-KeyPress>', agir9) # Appui sur n'importe quelle touche

    De façon générale pour les autres touches du clavier, cette utilisation basique demande de faire attention à l'utilisation conjointe de majuscule ou pas :

    1 2 3 4 5 6 7 8 9
    fenetre.bind('<KeyPress-a>', agir10) # Appui sur la touche A sans majuscule activée fenetre.bind('<KeyPress-b>', agir11) # Appui sur la touche B sans majuscule activée fenetre.bind('<KeyPress-c>', agir12) # Appui sur la touche C sans majuscule activée fenetre.bind('<KeyPress-d>', agir13) # Appui sur la touche D sans majuscule activée fenetre.bind('<KeyPress-A>', agir14) # Appui sur la touche A avec majuscule activée fenetre.bind('<KeyPress-B>', agir15) # Appui sur la touche B avec majuscule activée fenetre.bind('<KeyPress-C>', agir16) # Appui sur la touche C avec majuscule activée fenetre.bind('<KeyPress-D>', agir17) # Appui sur la touche D avec majuscule activée

    Si vous voulez d'autres explications :

    Passons à la pratique : nous allons rajouter une partie à notre programme de test : après la création de widgets, il est temps de surveiller quelques événements sur ceux-ci.

    16° Mettre le programme en mémoire.

    Questions :

    1. Sur quelle ligne se trouve la mise en place de la surveillance de certains événements ?
    2. Quel est l'événement surveillé ? Sur quels widgets ?
    3. Que provoque l'événement ?
    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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    # ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # Codification des contenus COULOIR = 0 MUR = 1 AVENTURIER = 3 DRAGON = 8 # texte à afficher - couleur - couleur au survol APPARENCE = { COULOIR: ('', '#EEEEEE', '#DDDDDD'), MUR: ('', 'black', '#111111'), AVENTURIER: ('A', '#8888FF', '#AAAAFF'), DRAGON: ('D', '#FF5555', '#FF7777') } # ----- 3 - FONCTIONS liées à la partie DONNEES ------------- # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py) # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y # Option : modification du widget (juste pour montrer qu'on peut !!) #if x == 1 : # nous sommes sur la 2e colonne (indice 1) # w[y][x].configure(bg='red') #elif y == 2 : # nous sommes sur la 3e ligne (indice 2) # w[y][x].configure(bg='#AA0000') # On renvoie la matrice return w def modifier_apparence_case(case, v): '''Modifie l'apparence du widget en fonction de la valeur v :: param case (tkinter.Label) :: la référence du widget-case à modifier :: param v (int) :: le code du contenu à afficher dans la case :: return (None) :: "procédure" Python .. effet de bord :: modifie le widget case ''' if v in APPARENCE.keys(): case.configure(text=APPARENCE[v][0]) case.configure(bg=APPARENCE[v][1]) def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets for y in range(nbl): for x in range(nbc): modifier_apparence_case(w[y][x], d[y][x]) def colorier(event): event.widget.configure(bg="#444444") # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, C par défaut C = COULOIR M = MUR A = AVENTURIER D = DRAGON donnees = [ [M, C, C, M, M, C], [M, C, D, M, M, C], [M, C, C, C, D, C], [A, C, C, C, M, M], [M, C, M, C, M, C], [M, C, M, C, C, C] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees) modifier_apparence_cases(widgets, donnees) # --- Création des événements nbl = len(widgets) nbc = len(widgets[0]) for y in range(nbl): for x in range(nbc): widgets[y][x].bind('<Enter>', colorier) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

    ...CORRECTION...

    1. Sur quelle lignes se trouve la mise en place de la surveillance de certains événements ?
    2. Ligne 156

    3. Quel est l'événement surveillé ? Sur quels widgets ?
    4. L'événement correspond à l'entrée de la souris dans un widget ('<Enter>'). La déclaration s'applique sur tous les widgets contenus dans la matrice widgets à l'aide de la double boucle.

    5. Que provoque l'événement ?
    6. Il faut aller voir le code de la fonction colorier. Lignes 113-114, on voit qu'elle ne fait que colorier le widget survoler en gris.

    Nous voulons maintenant rendre la couleur des widgets plus claire lorsqu'on les survole :

    17° Cette fois, la fonction-événementielle eclaircir est plus complexe car on a besoin de recevoir les matrices donnees et widgets de façon à pouvoir :

    1. Obtenir l'indice de ligne y et de colonne x du widget qu'on vient de survoler
    2. D'aller lire la donnée stockée dans donnees[y][x].
    3. De retrouver la couleur éclaircie à appliquer en cherchant dans APPARENCE (en utilisant la clé trouvée à l'étape précédente et en lisant l'indice 2 du tuple obtenu)
    4. D'appliquer cette couleur sur le widget en utilisant la méthode configure.

    Voici le prototype de la fonction eclaircir :

    113
    def eclaircir(e, w, d):

    Et la façon dont on lie cette fonction à un événement :

    183
    widgets[y][x].bind('<Enter>', lambda event: eclaircir(event, widgets, donnees))

    Questions liées au code ci-dessous (notamment le code surligné)

    1. Pourquoi a-t-on dû utiliser une fonction lambda lors de la déclaration de la fonction-événement ?
    2. Quelle est la fonction qui gère au final l'événement 'entrée dans la case' ?
    3. Quelle est la fonction qui gère au final l'événement 'sortie hors de la case' ?
    4. Sur quelles lignes avait-on créé les attributs ligne et colonne que nous parvenons ensuite à lire sur les lignes ... et ...
    5. Pourquoi aller chercher l'indice 2 du triplet stocké dans le dictionnaire ?
    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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    # ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # Codification des contenus COULOIR = 0 MUR = 1 AVENTURIER = 3 DRAGON = 8 # texte à afficher - couleur - couleur au survol APPARENCE = { COULOIR: ('', '#EEEEEE', '#FFFFFF'), MUR: ('', 'black', '#111111'), AVENTURIER: ('A', '#8888FF', '#AAAAFF'), DRAGON: ('D', '#FF5555', '#FF7777') } # ----- 3 - FONCTIONS liées à la partie DATA ---------------- # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py) # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y # Option : modification du widget (juste pour montrer qu'on peut !!) #if x == 1 : # nous sommes sur la 2e colonne (indice 1) # w[y][x].configure(bg='red') #elif y == 2 : # nous sommes sur la 3e ligne (indice 2) # w[y][x].configure(bg='#AA0000') # On renvoie la matrice return w def modifier_apparence_case(case, v): '''Modifie l'apparence du widget en fonction de la valeur v :: param case (tkinter.Label) :: la référence du widget-case à modifier :: param v (int) :: le code du contenu à afficher dans la case :: return (None) :: "procédure" Python .. effet de bord :: modifie le widget case ''' if v in APPARENCE.keys(): case.configure(text=APPARENCE[v][0]) case.configure(bg=APPARENCE[v][1]) def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets for y in range(nbl): for x in range(nbc): modifier_apparence_case(w[y][x], d[y][x]) def eclaircir(e, w, d): '''Modifie la case en utilisant la couleur claire :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' case = e.widget y = case.ligne x = case.colonne code = d[y][x] if code in APPARENCE: nouvelle_couleur = APPARENCE[code][2] case.configure(bg=nouvelle_couleur) def assombrir(e, w, d): '''Modifie la case en utilisant la couleur sombre :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' pass # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, C par défaut C = COULOIR M = MUR A = AVENTURIER D = DRAGON donnees = [ [M, C, C, M, M, C], [M, C, D, M, M, C], [M, C, C, C, D, C], [A, C, C, C, M, M], [M, C, M, C, M, C], [M, C, M, C, C, C] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees) modifier_apparence_cases(widgets, donnees) # --- Création des événements nbl = len(widgets) nbc = len(widgets[0]) for y in range(nbl): for x in range(nbc): widgets[y][x].bind('<Enter>', lambda event: eclaircir(event, widgets, donnees)) widgets[y][x].bind('<Leave>', lambda event: assombrir(event, widgets, donnees)) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

    ...CORRECTION...

    Rappel

    Les fonctions lambda ne sont pas au programme : nous ne les utiliserons que comme moyen commode de transmettre des paramètres lors de l'utilisation de la méthode bind ou after.

    Hors du contexte "Projet Tkinter", inutile d'essayer de placer une solution qui les utilise. Ce n'est pas attendu.

    18° En vous basant sur la fonction précédente, créer la fonction assombrir.

    ...CORRECTION...

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    def assombrir(e, w, d): '''Modifie la case en utilisant la couleur sombre :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' case = e.widget y = case.ligne x = case.colonne code = d[y][x] if code in APPARENCE: nouvelle_couleur = APPARENCE[code][1] case.configure(bg=nouvelle_couleur)

    Imaginons maintenant qu'on ne veuille pas animer les murs au survol. Les murs ne bougeront pas dans notre jeu. On peut donc juste récupérer la valeur du fond coloré de la case et s'il correspond à celui d'un mur, on ne fait rien. Pour cela, il faut utiliser la méthode cget.

    Méthode cget

    C'est une méthode propre aux widgets de Tkinter. Elle n'est donc pas à connaître. Elle permet de récupérer l'une des valeurs graphiques d'un widget : couleur du fond, couleur du texte, texte...

    Un exemple : on récupère la couleur du fond du widget dont la référence est stockée dans w_get.

    1
    couleur = case.cget('bg')

    Du coup, on peut l'utiliser pour modifier la couleur d'une des cases lorsqu'on la survole.

    19° Remplacer la fonction par la version ci-dessous. Expliquer ce que fait la ligne 127.

    113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
    def eclaircir(e, w, d): '''Modifie la case en utilisant la couleur claire :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' case = e.widget y = case.ligne x = case.colonne code = d[y][x] if case.cget('bg') != APPARENCE[MUR][1] and code in APPARENCE: nouvelle_couleur = APPARENCE[code][2] case.configure(bg=nouvelle_couleur)

    Question supplémentaire : quel est le type de MUR ?

    ...CORRECTION...

    On réalise l'éclaircissement SI :

    1. Le fond coloré de la case graphique n'est pas noir (APPARENCE[MUR][1])
    2. La clé fournie existe bien dans le dictionnaire APPARENCE

    MUR est une constante faisant référence à l'entier 1. Voir ligne 10. Il ne s'agit pas d'un string attention.

    Bien. Nous avons révisé comment gérer les événements dans Tkinter. Vous avez maintenant le squelette d'un plateau qui vous permettra de réaliser votre propre jeu. Avant cela, voyons comment gérer une modification dans la partie Donnée depuis la partie Interface.

    5 - Très Mini projet décrit : déplacer l'aventurier

    Avant de commencer, réfléchissons à la manière de procéder lorsque réalise un projet basé sur une IHM (Interface Homme Machine). Vous allez avoir besoin d'appliquer cette méthode de réflexion pendant votre projet.

    Un bon moyen de faire un projet en équipe est de :

    1. En groupe : réflechir avec un texte écrit en français qui décrit le déroulé des actions. On y trouve le nom des fonctions et ce qu'elles doivent faire
    2. En groupe : créer le schéma du projet pour vérifier qu'il est bien conçu (la partie Interface n'agit que sur l'Interface et la partie Données n'agit que sur les Données). Les interactions entre les deux catégories sont encapsulées dans certaines fonctions bien ciblées.
    3. En groupe : Réaliser les prototypages
    4. Individuel : Travailler sur sa fonction, créer sa documentation et des tests si possible.

    Un exemple - Parvenir à déplacer l'aventurier :

    On commence par réflechir aux fonctions qu'on désire, à ce qu'elle doive recevoir et à ce qu'elle doit faire ou répondre.

    1. Partie Données : on veut une fonction coordonnees_aventurier qui renvoie les x et y de la case-donnée contenant l'aventurier dans donnees.
    2. 1 2 3 4 5 6 7 8
      def coordonnees_aventurier(d): '''Renvoie un tuple (x,y) contenant les coordonnes de l'aventurier :: param d(list(list)) :: la matrice contenant les données des cases :: return (tuple(int,int)) :: (x,y) ''' return (0,0) # Fausse réponse qui permet néanmoins de travailler
    3. Partie Données : on veut une fonction est_case_proche qui renvoie True si les coordonnées-paramètres x et y correspondent à un élément adjacent verticalement ou horizontalement à la case-donnée cible (celle de l'aventurier ici).
    4. 1 2 3 4 5 6 7 8 9 10 11 12
      def est_case_proche(d, x, y, xA, yA): '''Renvoie True si d[y][x] est proche de la case cible d[yA][xA] :: param d(list(list)) :: la matrice contenant les données des cases :: param x(int) :: l'indice de la colonne de la case étudiée :: param y(int) :: l'indice de la ligne de la case étudiée :: param xA(int) :: l'indice de la colonne de la case cible :: param yA(int) :: l'indice de la ligne de la case cible :: return (bool) :: True si proche, False sinon ''' return True # Fausse réponse qui permet de travailler en attendant
    5. Partie Données : on veut une fonction intervertir qui intervertit les contenus de deux éléments de donnees dont on fournit les quatre coordonnées x1, y1, x2 et y2.
    6. 1 2 3 4 5 6 7 8 9 10 11 12 13
      def intervertir(d, x1, y1, x2, y2): '''Intervertit les contenus de d[y1][x1] et d[y2][x2] :: param d(list(list)) :: la matrice contenant les données des cases :: param x1(int) :: l'indice de la colonne de la 1er case :: param y1(int) :: l'indice de la ligne de la 1er case :: param x2(int) :: l'indice de la colonne de la 2e case :: param y2(int) :: l'indice de la ligne de la 2e case :: return (None) :: "Procédure" python .. effet de bord :: Modifie la matrice d ''' pass
    7. Partie Données : on crée une fonction tentative_deplacement qui reçoit des coordonnées x et y, qui vérifie si elles sont bien proches de l'aventurer et qui intervertit les éléments dans ce cas. Elle renvoie True si on fait une interversion.
    8. 1 2 3 4 5 6 7 8 9 10 11
      def tentative_deplacement(d, x, y): '''Tente de déplacer l'aventurier et renvoie True si cela fonctionne :: param d(list(list)) :: la matrice contenant les données des cases :: param x(int) :: l'indice de la colonne de la case étudiée :: param y(int) :: l'indice de la ligne de la case étudiée :: return (bool) :: True si déplacement possible et effectué .. effet de bord :: Modifie la matrice d si déplacement possible ''' return True # Fausse réponse qui permet de travailler en attendant
    9. Partie Interface : on crée une fonction gerer_clic_case liée aux clics sur l'une des cases et qui
      • récupère la référence de la case-widget, sa ligne et sa colonne
      • envoie cette ligne et cette colonne à la fonction tentative_deplacement appartenant à la partie Données.
      • si tentative_deplacement répond True, on actualise l'affichage de l'interface avec la fonction modifier_apparence_cases (déja codée)
      1 2 3 4 5 6 7 8 9 10 11
      def gerer_clic_case(e, w, d): '''Tente de faire bouge l'aventurier sur le plateau :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w et la matrice d ''' modifier_apparence_cases(w, d) # en attendant mieux

    Si on représente la répartition entre partie Interface et partie Données, cela donne :

    Description du projet Aventurier

    Pour travailler en équipe, vous allez donc devoir avoir au moins le prototype des fonctions (avec la documentation précises des préconditions et postconditions).

    Chaque personne du groupe peut ensuite commencer à réfléchir sur sa propre fonction.

    20° Quel est l'intérêt de placer de "fausses réponses" dans les fonctions devant répondre plutôt qu'un simple pass ?

    ...CORRECTION...

    Cela permet simplement de commencer à travailler même si on a besoin d'une réponse de l'une de ces fonctions. On sait que la réponse est fausse mais on n'a pas à entendre la version finale pour avancer.

    Voici le code final du très mini-projet Déplacement Aventurier. Vous pouvez le lancer, le tester et voir comment je propose de résoudre les problèmes. Bien entendu, il a d'autres manières de coder une même fonction.

    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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
    # ----- 1 - IMPORTATIONS ------------------------------------ import tkinter as tk # ----- 2 - CONSTANTES -------------------------------------- # Codification des contenus COULOIR = 0 MUR = 1 AVENTURIER = 3 DRAGON = 8 # texte à afficher - couleur - couleur au survol APPARENCE = { COULOIR: ('', '#EEEEEE', '#FFFFFF'), MUR: ('', 'black', '#111111'), AVENTURIER: ('A', '#8888FF', '#AAAAFF'), DRAGON: ('D', '#FF5555', '#FF7777') } # ----- 3 - FONCTIONS liées à la partie DATA ---------------- def coordonnees_aventurier(d): '''Renvoie un tuple (x,y) contenant les coordonnes de l'aventurier :: param d(list(list)) :: la matrice contenant les données des cases :: return (tuple(int,int)) :: (x,y) ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes for y in range(nbl): for x in range(nbc): if d[y][x] == AVENTURIER: return (x,y) def est_case_proche(d, x, y, xA, yA): '''Renvoie True si d[y][x] est proche de la case cible d[yA][xA] :: param d(list(list)) :: la matrice contenant les données des cases :: param x(int) :: l'indice de la colonne de la case étudiée :: param y(int) :: l'indice de la ligne de la case étudiée :: param xA(int) :: l'indice de la colonne de la case cible :: param yA(int) :: l'indice de la ligne de la case cible :: return (bool) :: True si proche, False sinon ''' if (x-1) == xA and y == yA: return True elif (x+1) == xA and y == yA: return True elif x == xA and (y-1) == yA: return True elif x == xA and (y+1) == yA: return True else: return False def intervertir(d, x1, y1, x2, y2): '''Intervertit les contenus de d[y1][x1] et d[y2][x2] :: param d(list(list)) :: la matrice contenant les données des cases :: param x1(int) :: l'indice de la colonne de la 1er case :: param y1(int) :: l'indice de la ligne de la 1er case :: param x2(int) :: l'indice de la colonne de la 2e case :: param y2(int) :: l'indice de la ligne de la 2e case :: return (None) :: "Procédure" python .. effet de bord :: Modifie la matrice d ''' temp = d[y1][x1] d[y1][x1] = d[y2][x2] d[y2][x2] = temp def tentative_deplacement(d, x, y): '''Tente de déplacer l'aventurier et renvoie True si cela fonctionne :: param d(list(list)) :: la matrice contenant les données des cases :: param x(int) :: l'indice de la colonne de la case étudiée :: param y(int) :: l'indice de la ligne de la case étudiée :: return (bool) :: True si déplacement possible et effectué .. effet de bord :: Modifie la matrice d si déplacement possible ''' (xA, yA) = coordonnees_aventurier(d) if est_case_proche(d, x, y, xA, yA): intervertir(d, x, y, xA, yA) return True else: return False # ----- 4 - FONCTIONS liées à la partie INTERFACE ----------- def configurer_fenetre(fe): '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fe.geometry("1200x600") fe.title("Mon super jeu") fe.configure(bg="black") def creer_cases(fe, d): '''Renvoie une matrice ayant les mêmes dimensions que dc et contenant des widgets Labels :: param fe(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param d(list(list)) :: la matrice contenant les données des cases :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant des tkinter.Label ''' nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On crée une matrice w qui servira à contenir les widgets représentant les cases w = [ [None for x in range(nbc)] for y in range(nbl) ] # On crée les Labels un par un et on les stocke dans la matrice for y in range(nbl): for x in range(nbc): # Calcul du numéro de la case numero = x + y * nbc # Création et stockage d'un Label w[y][x] = tk.Label(fe, text=numero, fg="white", bg='grey', width=10, height=5) # Affichage et placement du widget contenu dans w[y][x] px = 20 + x * 90 # position px en pixels py = 20 + y * 85 # position py en pixels w[y][x].place(x=px, y=py) # Option : rajout d'attributs pour les récupérer facilement w[y][x].numero = numero w[y][x].colonne = x w[y][x].ligne = y # Option : modification du widget (juste pour montrer qu'on peut !!) #if x == 1 : # nous sommes sur la 2e colonne (indice 1) # w[y][x].configure(bg='red') #elif y == 2 : # nous sommes sur la 3e ligne (indice 2) # w[y][x].configure(bg='#AA0000') # On renvoie la matrice return w def modifier_apparence_case(case, v): '''Modifie l'apparence du widget en fonction de la valeur v :: param case (tkinter.Label) :: la référence du widget-case à modifier :: param v (int) :: le code du contenu à afficher dans la case :: return (None) :: "procédure" Python .. effet de bord :: modifie le widget case ''' if v in APPARENCE.keys(): case.configure(text=APPARENCE[v][0]) case.configure(bg=APPARENCE[v][1]) def modifier_apparence_cases(w, d): '''Modifie les widgets pour refléter les données du plateau de jeu :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' # On récupére les dimensions de la matrice d nbl = len(d) # Nombre de lignes nbc = len(d[0]) # Nombre de lignes # On lit les données une par une et on modifie les widgets for y in range(nbl): for x in range(nbc): modifier_apparence_case(w[y][x], d[y][x]) def eclaircir(e, w, d): '''Modifie la case en utilisant la couleur claire :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' case = e.widget y = case.ligne x = case.colonne code = d[y][x] if case.cget('bg') != APPARENCE[MUR][1] and code in APPARENCE: nouvelle_couleur = APPARENCE[code][2] case.configure(bg=nouvelle_couleur) def assombrir(e, w, d): '''Modifie la case en utilisant la couleur sombre :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w ''' case = e.widget y = case.ligne x = case.colonne code = d[y][x] if case.cget('bg') != APPARENCE[MUR][1] and code in APPARENCE: nouvelle_couleur = APPARENCE[code][1] case.configure(bg=nouvelle_couleur) def gerer_clic_case(e, w, d): '''Tente de faire bouge l'aventurier sur le plateau :: param e (tkinter.Event) :: contient les informations sur l'événement :: param w (list(list)) :: matrice contenant les références des widgets :: param d (list(list)) :: matrice contenant les données des cases :: return (None) :: "procédure" Python .. effet de bord :: modifie la matrice w et la matrice d ''' case = e.widget x = case.colonne y = case.ligne if tentative_deplacement(d, x, y): modifier_apparence_cases(w, d) # ----- 5 - PROGRAMME PRINCIPAL ------------------------------ if __name__ == '__main__': # --- Activation des tests sur les fonctions du module import doctest doctest.testmod() # --- Données du jeu # informations diverses : complement = [-1, 0] # Indice 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # matrice encodant les données du plateau, C par défaut C = COULOIR M = MUR A = AVENTURIER D = DRAGON donnees = [ [M, C, C, M, M, C], [M, C, D, M, M, C], [M, C, C, C, D, C], [A, C, C, C, M, M], [M, C, M, C, M, C], [M, C, M, C, C, C] ] # --- Création de la fenêtre principale fenetre = tk.Tk() configurer_fenetre(fenetre) # --- Création des widgets représentant graphiquement les cases widgets = creer_cases(fenetre, donnees) modifier_apparence_cases(widgets, donnees) # --- Création des événements nbl = len(widgets) nbc = len(widgets[0]) for y in range(nbl): for x in range(nbc): widgets[y][x].bind('<Enter>', lambda event: eclaircir(event, widgets, donnees)) widgets[y][x].bind('<Leave>', lambda event: assombrir(event, widgets, donnees)) widgets[y][x].bind('<Button-1>', lambda event: gerer_clic_case(event, widgets, donnees)) # --- Activation de la surveillance sur l'application graphique fenetre.mainloop()

    Si vous voulez plus d'informations, vous pouvez faire des recherches sur le Web.

    Vous trouverez également quelques fiches sur ce site, comme celle-ci par exemple.

    FICHE Tkinter

    Activité publiée le 29 11 2019
    Dernière modification : 13 12 2020
    Auteur : ows. h.