Infoforall

Python 17 : Outils et astuces pour limiter la taille du code

De nombreuses lignes très similaires sont parfois tapées. C'est souvent une perte de temps mais, surtout, cela met en péril la maintenance ou la modification du code : imaginons qu'on veuille modifier la façon dont on gère une suite d'actions. Si cette suite d'actions est reproduite à 25 endroits différents dans le code, il est quasi-certain que vous oublierez d'en modifier au moins une. Et du coup, cela va créer une erreur.

Cette activté vous propose plusieurs outils permettant d'augmenter la part de code automatisé. Cela vous évitera d'avoir à trop souvent taper des lignes similaires.

1 - Les paramètres par défaut

Nous avons vu que certaines méthodes possèdents des paramètres qui peuvent prendre une valeur par défaut si on ne transmet pas d'arguments. Mais comment est-ce que cela fonctionne ?

En fait, c'est très simple. Commençons par créer une petite fonction qui crée un bouton en fonction :

  1. du widget maitre
  2. d'un texte à afficher
  3. des coordonnées x et y à utiliser avec place,
  4. d'une fonction à fournir,
  5. d'une couleur de fond,
  6. d'une couleur d'écrite
  7. d'une police d'écriture.
  8. d'une largeur et d'une hauteur

#!/usr/bin/env python

# -*- coding: utf-8 -*-


from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def action_bouton() :

    pass


def creation_bouton( widgetMaitre, texte, coordX, coordY, fonctionBouton, couleurFond, couleurTexte, policeTexte, largeur, hauteur ) :

    """

    Permet de créer un widget Bouton typique de l'application.

    Le paramètre widgetMaitre doit contenir le widget dans lequel afficher le bouton.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètre texte contient le message à afficher.

    Les paramètres coordX coordY sont les coordonnées pour place.

    Le paramètre fonctionBouton doit renvoyer à la fonction voulue.

    Les paramètres couleurFond, couleurTexte et policeTexte gère les attributs bg,fg et font.

    Les paramètres largeur, hauteur sont associés à width et height.

    """

    widLabel = Button( widgetMaitre, text = texte, command = fonctionBouton, bg = couleurFond, fg = couleurTexte, font = policeTexte, width = largeur, height = hauteur )

    widLabel.place( x = coordX, y = coordY )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# Création des widgets


widBouton1 = creation_bouton( fen_princ, "Premier Bouton", 50, 50, action_bouton, "#991111", "#44DDDD", ("Helvetica", 9), 20, 3 )

widBouton2 = creation_bouton( fen_princ, "Second Bouton", 210, 50, action_bouton, "#991111", "#44DDDD", ("Helvetica", 9), 20, 3 )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

01° Lancer le code via IDLE pour tester le résultat. Passer en console Python IDLE et taper :

>>> print( creation_bouton.__doc__ )

Vous devriez voir apparaitre le texte que vous avez inséré au début de la fonction. Cela se nomme documenter sa fonction. L'intérêt est limité au début mais rapidement, les programmes deviennent complexes et on ne souvient plus forcément de tout ce qu'ils font et pourquoi et comment. De plus, l'informatique est une activité qui se réalise le plus souvent en groupe : quelqu'un aura donc certainement besoin un jour de modifier votre code. Encore faut-il qu'il le comprenne !

Permet de créer un widget Bouton typique de l'application.

Le paramètre widgetMaitre doit contenir le widget dans lequel afficher le bouton.

Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

Le paramètre texte contient le message à afficher

Les paramètres coordX coordY sont les coordonnées pour place.

Le paramètre fonctionBouton doit renvoyer à la fonction voulue.

Les paramètres couleurFond, couleurTexte et policeTexte gère les attributs bg,fg et font.

Les paramètres largeur, hauteur sont associés à width et height.

Nous aurions pu aussi taper ceci :

>>> help( creation_console )

N'empêche que ça ne résoud pas notre problème : il y a beaucoup de valeur à fournir pour notre fonction de création ...

De manière générale, on peut considérer qu'une fonction qui a besoin de plus de 3 arguments à fournir est difficile à appréhender pour quelqu'un d'autre que son créateur. Comment faire alors ?

Et bien, c'est simple : on peut fournir des valeurs par défaut lors de la déclaration de la fonction.

Exemple : le code version 2 avec des valeurs par défaut :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def action_bouton() :

    pass


def creation_bouton( widgetMaitre, texte, coordX, coordY, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) :

    """

    Permet de créer un widget Bouton typique de l'application.

    Le paramètre widgetMaitre doit contenir le widget dans lequel afficher le bouton.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètre texte contient le message à afficher.

    Les paramètres coordX coordY sont les coordonnées pour place.

    Le paramètre fonctionBouton doit renvoyer à la fonction voulue.

    Les paramètres couleurFond, couleurTexte et policeTexte gère les attributs bg,fg et font.

    Les paramètres largeur, hauteur sont associés à width et height.

    """

    widLabel = Button( widgetMaitre, text = texte, command = fonctionBouton, bg = couleurFond, fg = couleurTexte, font = policeTexte, width = largeur, height = hauteur )

    widLabel.place( x = coordX, y = coordY )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# Création des widgets


widBouton1 = creation_bouton( fen_princ, "Premier Bouton", 50, 50 )

widBouton2 = creation_bouton( fen_princ, "Second Bouton", 210, 50 )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

02° Donne-t-on des valeurs de couleur ou de taille lors de l'appel de la fonction ? Pourquoi les deux boutons sont-ils noirs avec une écriture jaune ?

...CORRECTION...

On ne donne que 4 arguments lors de l'appel de la fonction : les 4 premiers.

Comme on ne fournit rien pour les suivants, l'interpréteur utilise donc les valeurs proposées par défaut.

Cela revient donc à avoir faire l'appel de .

widBouton1 = creation_bouton( fen_princ, "Premier Bouton", 50, 50, action_bouton, "black", "yellow", ("Helvetica", 9), 20, 3 )

Reste le problème de la fenêtre principale : j'aimerai aussi pouvoir ne pas la fournir à chaque fois. Ca risque d'être souvent ...

03° Rajouter fen_princ en valeur par défaut du premier paramète. Supprimer cet argument lors de l'appel de la fonction. Lancer. A votre avis, pourquoi ça ne fonctionne pas ?

def creation_bouton( widgetMaitre = fen_princ, texte, coordX, coordY, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) :

widBouton1 = creation_bouton( "Premier Bouton", 50, 50 )

widBouton2 = creation_bouton( "Second Bouton", 210, 50 )

...CORRECTION...

Comme souvent l'explication est simple une fois qu'on nous le dit ! Si vous avez trouvé tout seul, chapeau.

Ca ne fonctionne pas car l'ordinateur remplit les paramètres dans l'ordre des arguments fournis. Il ne sait pas ce qu'on lui donne. Il prend et range dans l'ordre. Ainsi, il range :

"Premier Bouton" dans le paramètre widgetMaitre

50 dans le paramètre texte

210 dans le paramètre coordX.

Du coup, ca ne fonctionne pas très bien...

Comme faire alors ? On pourrait bien entendu mettre le paramètre widgetMaitre en dernier. Mais concrétement, cela veut dire qu'on peut ne pas fournir un argument mais que dans ce cas, on doit omettre également tous ceux qui suivent.

Exemple :

Puisque l'ordre de déclaration des paramètres est le suivant :

  1. widgetMaitre = fen_princ,
  2. texte,
  3. coordX,
  4. coordY,
  5. fonctionBouton = action_bouton,
  6. couleurFond = 'black',
  7. couleurTexte = 'yellow',
  8. policeTexte = ("Helvetica", 9),
  9. largeur = 20,
  10. hauteur = 3

Si je ne fournis pas largeur, je ne peux pas fournir hauteur.

Si je ne fournis pas policeTexte, je ne peux fournir ni largeur, ni hauteur.

Si je ne fournis pas couleurTexte, je ne peux fournir ni policeTexte, ni largeur, ni hauteur.

Bref, c'est assez limitant.

Heureusement, on peut encore faire mieux.

2 - Etiquettes : les paramètres nommés

Jusqu'à présent, nous avions fourni des arguments dans l'ordre des paramètres. Nous allons maintenant faire des appels de fonctions en utilisant les noms des paramètres, leurs étiquettes. Par exemple :

widBouton1 = creation_bouton( texte = "Premier Bouton", coordX = 50, coordY = 50 )

widBouton2 = creation_bouton( texte = "Second Bouton", coordX = 210, coordY = 50 )

Cela oblige l'utilisateur de la fonction à connaitre le nom des paramètres lorsqu'il fournit les arguments. Cela n'a pas trop d'incidence sur l'encapsulation : après tout, il devait déjà en connaitre l'ordre.

Par contre, pour faire cela, il faut que tous les paramètres possèdent une valeur par défaut.

def creation_bouton( widgetMaitre = fen_princ, texte = "", coordX = 0, coordY = 0, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) :

04° Utiliser le code fourni. Lire l'erreur et tenter de corriger le problème.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def action_bouton() :

    pass


def creation_bouton( widgetMaitre = fen_princ, texte = "", coordX = 0, coordY = 0, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) :

    """

    Permet de créer un widget Bouton typique de l'application.

    Le paramètre widgetMaitre doit contenir le widget dans lequel afficher le bouton.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètre texte contient le message à afficher.

    Les paramètres coordX coordY sont les coordonnées pour place.

    Le paramètre fonctionBouton doit renvoyer à la fonction voulue.

    Les paramètres couleurFond, couleurTexte et policeTexte gère les attributs bg,fg et font.

    Les paramètres largeur, hauteur sont associés à width et height.

    """

    widLabel = Button( widgetMaitre, text = texte, command = fonctionBouton, bg = couleurFond, fg = couleurTexte, font = policeTexte, width = largeur, height = hauteur )

    widLabel.place( x = coordX, y = coordY )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# Création des widgets


widBouton1 = creation_bouton( texte = "Premier Bouton", coordX = 50, coordY = 50 )

widBouton2 = creation_bouton( texte = "Second Bouton", coordX = 210, coordY = 50 )

# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

...CORRECTION...

On obtient l'erreur suivante :

def creation_bouton( widgetMaitre=fen_princ , texte ="", coordX=0, coordY=0, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) : NameError: name 'fen_princ' is not defined

Le truc important est à la fin : NameError: name 'fen_princ' is not defined

Pourquoi ?

Car on tente de fournir par défaut une variable qui n'est pas encore créé puisqu'on la déclare APRES la déclaration de la fonction.

La solution ? Faire la déclaration AVANT la fonction.

05° Vérifier la validité du code suivant. Localiser la déclaration de la fenêtre principale de façon à comprendre pourquoi cela ne provoque plus d'erreur.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclaration des variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def action_bouton() :

    pass


def creation_bouton( widgetMaitre = fen_princ, texte = "", coordX = 0, coordY = 0, fonctionBouton = action_bouton, couleurFond = 'black', couleurTexte = 'yellow', policeTexte = ("Helvetica", 9), largeur = 20, hauteur = 3 ) :

    """

    Permet de créer un widget Bouton typique de l'application.

    Le paramètre widgetMaitre doit contenir le widget dans lequel afficher le bouton.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètre texte contient le message à afficher.

    Les paramètres coordX coordY sont les coordonnées pour place.

    Le paramètre fonctionBouton doit renvoyer à la fonction voulue.

    Les paramètres couleurFond, couleurTexte et policeTexte gère les attributs bg,fg et font.

    Les paramètres largeur, hauteur sont associés à width et height.

    """

    widLabel = Button( widgetMaitre, text = texte, command = fonctionBouton, bg = couleurFond, fg = couleurTexte, font = policeTexte, width = largeur, height = hauteur )

    widLabel.place( x = coordX, y = coordY )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Création des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des widgets


widBouton1 = creation_bouton( texte = "Premier Bouton", coordX = 50, coordY = 50 )

widBouton2 = creation_bouton( texte = "Second Bouton", coordX = 210, coordY = 50 )

# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Avec 2 boutons, vous n'avez peut-être pas l'impression qu'on ai gagné en longueur de code. Si votre programme doit créer une trentaine de boutons, je vous assure que cela vous rendra bien service.

A titre d'exercice, vous allez reprendre le programme Simili-Simon et vous allez utiliser ces nouvelles connaissances pour créer une fonction de création de Label avec paramètres nommés et possédant des valeurs par défaut.

Remarque : j'ai déplacé la création de la fenêtre en début de code car nous allons avoir besoin de créer des images Tk dans une fonction. Cela provoque si une erreur si la fenêtre Tk n'est pas encore créée.

Remarque 2 : si vous avez du mal à comprendre le code, allez revoir la partie MINI-PROJET de l'activité sur la portée des variables. Le code y est décrit presque ligne par ligne.

06° A vous de modifier le code :

N'oubliez pas de modifier les appels à ces fonctions.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -



fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge, vert, bleu) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(coordX, coordY, imageDeBase) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètres imageDeBase correspond à la référence de l'image qu'on veut afficher initialement.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """

    widLabel = Label(fen_princ, image = imageDeBase)

    widLabel.place(x = coordX, y = coordY)

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = carreBleuA_ON)

    elif action == "B ON" :

        caseB.configure(image = carreVertB_ON)

    elif action == "C ON" :

        caseC.configure(image = carreRougeC_ON)

    elif action == "D ON" :

        caseD.configure(image = carreJauneD_ON)

    elif action == "A OFF" :

        caseA.configure(image = carreBleuA_OFF)

    elif action == "B OFF" :

        caseB.configure(image = carreVertB_OFF)

    elif action == "C OFF" :

        caseC.configure(image = carreRougeC_OFF)

    elif action == "D OFF" :

        caseD.configure(image = carreJauneD_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = carreBleuA_ON)

        caseB.configure(image = carreVertB_ON)

        caseC.configure(image = carreRougeC_ON)

        caseD.configure(image = carreJauneD_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = carreBleuA_OFF)

        caseB.configure(image = carreVertB_OFF)

        caseC.configure(image = carreRougeC_OFF)

        caseD.configure(image = carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


def changeA_ON(event) :

    """Fonction-événement qui modifie le carré A si l'animation n'est pas active."""

    if animation == False :

        modifier_case("A ON")

        listeDesActions.append('A ON')


def changeA_OFF(event) :

    """Fonction-événement qui modifie le carré A si l'animation n'est pas active."""

    if animation == False :

        modifier_case("A OFF")

        listeDesActions.append('A OFF')


def changeB_ON(event) :

    """Fonction-événement qui modifie le carré B si l'animation n'est pas active."""

    if animation == False :

        modifier_case("B ON")

        listeDesActions.append('B ON')


def changeB_OFF(event) :

    """Fonction-événement qui modifie le carré B si l'animation n'est pas active."""

    if animation == False :

        modifier_case("B OFF")

        listeDesActions.append('B OFF')


def changeC_ON(event) :

    """Fonction-événement qui modifie le carré C si l'animation n'est pas active."""

    if animation == False :

        modifier_case("C ON")

        listeDesActions.append('C ON')


def changeC_OFF(event) :

    """Fonction-événement qui modifie le carré C si l'animation n'est pas active."""

    if animation == False :

        modifier_case("C OFF")

        listeDesActions.append('C OFF')


def changeD_ON(event) :

    """Fonction-événement qui modifie le carré D si l'animation n'est pas active."""

    if animation == False :

        modifier_case("D ON")

        listeDesActions.append('D ON')


def changeD_OFF(event) :

    """Fonction-événement qui modifie le carré D si l'animation n'est pas active."""

    if animation == False :

        modifier_case("D OFF")

        listeDesActions.append('D OFF')


# - - - - - - - - - - - - - - - - - -

# Création des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des images de base


carreBleuA_OFF = creation_image_tk(0,0,200)

carreBleuA_ON = creation_image_tk(75,75,250)

carreVertB_OFF = creation_image_tk(0,200,0)

carreVertB_ON = creation_image_tk(75,250,75)

carreRougeC_OFF = creation_image_tk(200,0,0)

carreRougeC_ON = creation_image_tk(250,75,75)

carreJauneD_OFF = creation_image_tk(200,200,0)

carreJauneD_ON = creation_image_tk(250,250,0)


# Création et placement des widgets Label


caseA = creation_widget_case(100,100,carreBleuA_OFF)

caseB = creation_widget_case(300,100,carreVertB_OFF)

caseC = creation_widget_case(100,300,carreRougeC_OFF)

caseD = creation_widget_case(300,300,carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

caseA.bind( '<Button-1>', changeA_ON )

caseB.bind( '<Button-1>', changeB_ON )

caseC.bind( '<Button-1>', changeC_ON )

caseD.bind( '<Button-1>', changeD_ON )


# Associe le relachement du bouton-gauche à une fonction-événement

caseA.bind( '<ButtonRelease-1>', changeA_OFF )

caseB.bind( '<ButtonRelease-1>', changeB_OFF )

caseC.bind( '<ButtonRelease-1>', changeC_OFF )

caseD.bind( '<ButtonRelease-1>', changeD_OFF )


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

...CORRECTION...

Le code complet est visible au début de la partie suivante, celle sur les fonctions lambda

Choix des noms des paramètres : le choix des noms coordX et coordY fournit une explication claire du contenu. Mais prendre simplement x et y aurait rendu l'appel des fonctions plus rapide.

3 - Fonction lambda

L'un des problèmes des boutons et plus généralement de la gestion des événements est qu'on doit fournir une fonction sans argument : on donne juste le nom de la fonction :

# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

caseA.bind( '<Button-1>', changeA_ON )

caseB.bind( '<Button-1>', changeB_ON )

caseC.bind( '<Button-1>', changeC_ON )

caseD.bind( '<Button-1>', changeD_ON )


# Associe le relachement du bouton-gauche à une fonction-événement

caseA.bind( '<ButtonRelease-1>', changeA_OFF )

caseB.bind( '<ButtonRelease-1>', changeB_OFF )

caseC.bind( '<ButtonRelease-1>', changeC_OFF )

caseD.bind( '<ButtonRelease-1>', changeD_OFF )


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )

Si on regarde les fonctions-événements changeA_ON, changeB_ON, changeC_ON ..., on se rend compte qu'elles sont presque toutes similaires : on les utilise juste pour fournir des arguments à une autre fonction !

def changeA_ON(event) :

    if animation == False :

        modifier_case("A ON")

        listeDesActions.append('A ON')

def changeB_ON(event) :

    if animation == False :

        modifier_case("B ON")

        listeDesActions.append('B ON')

def changeC_ON(event) :

    if animation == False :

        modifier_case("C ON")

        listeDesActions.append('C ON')

En gros, on pourrait faire cela avec une seule fonction possédant deux arguments en plus du event :

def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)

Si on pouvait donner des fonctions avec arguments lors de la création de l'événements, on pourrait alors juste écrire :

# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

caseA.bind( '<Button-1>', change("A","ON") )

caseB.bind( '<Button-1>', change("B","ON") )

caseC.bind( '<Button-1>', change("C","ON") )

caseD.bind( '<Button-1>', change("D","ON") )


# Associe le relachement du bouton-gauche à une fonction-événement

caseA.bind( '<ButtonRelease-1>', change("A","OFF") )

caseB.bind( '<ButtonRelease-1>', change("B","OFF") )

caseC.bind( '<ButtonRelease-1>', change("C","OFF") )

caseD.bind( '<ButtonRelease-1>', change("D","OFF") )


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )

Ben oui. Mais ça ne fonctionne pas. Avec des si, on en ferait des choses dans le monde. Alors ? Comment s'en sortir ?

Réponse : avec les fonctions anonymes ou autrement nommées, les fonctions lambda. Contrairement aux fonctions déclarées avec def, on ne leur donne pas de nom d'alias et on fait précédé leur code du mot-clé ... lambda.

07° Utiliser le code fourni (qui pourra vous servir de correction pour la question 6) où l'un des événements est géré par une fonction lambda. Corriger les autres événements puis supprimer les fonctions qui ne servent plus à rien.

En analysant le code, vous pourrez voir que l'événement utilise la fonction change vue plus haut.

caseA.bind( '<Button-1>', lambda event : change(event,"A","ON") )

Alors qu'avant, on avait :

caseA.bind( '<ButtonRelease-1>', changeA_OFF )

Voici le code à tester et modifier :

caseA.bind( '<Button-1>', lambda event : change(event,"A","ON")

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(coordX = 100, coordY = 100, imageDeBase = creation_image_tk()) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètres imageDeBase correspond à la référence de l'image qu'on veut afficher initialement.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """

    widLabel = Label(fen_princ, image = imageDeBase)

    widLabel.place(x = coordX, y = coordY)

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = carreBleuA_ON)

    elif action == "B ON" :

        caseB.configure(image = carreVertB_ON)

    elif action == "C ON" :

        caseC.configure(image = carreRougeC_ON)

    elif action == "D ON" :

        caseD.configure(image = carreJauneD_ON)

    elif action == "A OFF" :

        caseA.configure(image = carreBleuA_OFF)

    elif action == "B OFF" :

        caseB.configure(image = carreVertB_OFF)

    elif action == "C OFF" :

        caseC.configure(image = carreRougeC_OFF)

    elif action == "D OFF" :

        caseD.configure(image = carreJauneD_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = carreBleuA_ON)

        caseB.configure(image = carreVertB_ON)

        caseC.configure(image = carreRougeC_ON)

        caseD.configure(image = carreJauneD_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = carreBleuA_OFF)

        caseB.configure(image = carreVertB_OFF)

        caseC.configure(image = carreRougeC_OFF)

        caseD.configure(image = carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)

def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


def changeA_ON(event) :

    """Fonction-événement qui modifie le carré A si l'animation n'est pas active."""

    if animation == False :

        modifier_case("A ON")

        listeDesActions.append('A ON')


def changeA_OFF(event) :

    """Fonction-événement qui modifie le carré A si l'animation n'est pas active."""

    if animation == False :

        modifier_case("A OFF")

        listeDesActions.append('A OFF')


def changeB_ON(event) :

    """Fonction-événement qui modifie le carré B si l'animation n'est pas active."""

    if animation == False :

        modifier_case("B ON")

        listeDesActions.append('B ON')


def changeB_OFF(event) :

    """Fonction-événement qui modifie le carré B si l'animation n'est pas active."""

    if animation == False :

        modifier_case("B OFF")

        listeDesActions.append('B OFF')


def changeC_ON(event) :

    """Fonction-événement qui modifie le carré C si l'animation n'est pas active."""

    if animation == False :

        modifier_case("C ON")

        listeDesActions.append('C ON')


def changeC_OFF(event) :

    """Fonction-événement qui modifie le carré C si l'animation n'est pas active."""

    if animation == False :

        modifier_case("C OFF")

        listeDesActions.append('C OFF')


def changeD_ON(event) :

    """Fonction-événement qui modifie le carré D si l'animation n'est pas active."""

    if animation == False :

        modifier_case("D ON")

        listeDesActions.append('D ON')


def changeD_OFF(event) :

    """Fonction-événement qui modifie le carré D si l'animation n'est pas active."""

    if animation == False :

        modifier_case("D OFF")

        listeDesActions.append('D OFF')


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des images de base


carreBleuA_OFF = creation_image_tk(bleu = 200)

carreBleuA_ON = creation_image_tk(rouge = 75, vert = 75, bleu = 250)

carreVertB_OFF = creation_image_tk(vert = 200)

carreVertB_ON = creation_image_tk(rouge = 75, vert = 250, bleu = 75)

carreRougeC_OFF = creation_image_tk(rouge = 200)

carreRougeC_ON = creation_image_tk(rouge = 250,vert = 75,bleu = 75)

carreJauneD_OFF = creation_image_tk(rouge = 200, vert = 200)

carreJauneD_ON = creation_image_tk(rouge = 250, vert = 250)


# Création et placement des widgets Label


caseA = creation_widget_case( imageDeBase = carreBleuA_OFF )

caseB = creation_widget_case( coordX = 300, imageDeBase = carreVertB_OFF )

caseC = creation_widget_case( coordY = 300, imageDeBase = carreRougeC_OFF )

caseD = creation_widget_case( coordX = 300, coordY = 300, imageDeBase = carreJauneD_OFF )


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

caseA.bind( '<Button-1>', lambda event : change(event,"A","ON") )

caseB.bind( '<Button-1>', changeB_ON )

caseC.bind( '<Button-1>', changeC_ON )

caseD.bind( '<Button-1>', changeD_ON )


# Associe le relachement du bouton-gauche à une fonction-événement

caseA.bind( '<ButtonRelease-1>', changeA_OFF )

caseB.bind( '<ButtonRelease-1>', changeB_OFF )

caseC.bind( '<ButtonRelease-1>', changeC_OFF )

caseD.bind( '<ButtonRelease-1>', changeD_OFF )


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Après correction et suppression des fonctions qui ne servent plus à rien, on gagne pas mal de lignes :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(coordX = 100, coordY = 100, imageDeBase = creation_image_tk()) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètres imageDeBase correspond à la référence de l'image qu'on veut afficher initialement.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """

    widLabel = Label(fen_princ, image = imageDeBase)

    widLabel.place(x = coordX, y = coordY)

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = carreBleuA_ON)

    elif action == "B ON" :

        caseB.configure(image = carreVertB_ON)

    elif action == "C ON" :

        caseC.configure(image = carreRougeC_ON)

    elif action == "D ON" :

        caseD.configure(image = carreJauneD_ON)

    elif action == "A OFF" :

        caseA.configure(image = carreBleuA_OFF)

    elif action == "B OFF" :

        caseB.configure(image = carreVertB_OFF)

    elif action == "C OFF" :

        caseC.configure(image = carreRougeC_OFF)

    elif action == "D OFF" :

        caseD.configure(image = carreJauneD_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = carreBleuA_ON)

        caseB.configure(image = carreVertB_ON)

        caseC.configure(image = carreRougeC_ON)

        caseD.configure(image = carreJauneD_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = carreBleuA_OFF)

        caseB.configure(image = carreVertB_OFF)

        caseC.configure(image = carreRougeC_OFF)

        caseD.configure(image = carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des images de base


carreBleuA_OFF = creation_image_tk(bleu = 200)

carreBleuA_ON = creation_image_tk(rouge = 75, vert = 75, bleu = 250)

carreVertB_OFF = creation_image_tk(vert = 200)

carreVertB_ON = creation_image_tk(rouge = 75, vert = 250, bleu = 75)

carreRougeC_OFF = creation_image_tk(rouge = 200)

carreRougeC_ON = creation_image_tk(rouge = 250,vert = 75,bleu = 75)

carreJauneD_OFF = creation_image_tk(rouge = 200, vert = 200)

carreJauneD_ON = creation_image_tk(rouge = 250, vert = 250)


# Création et placement des widgets Label


caseA = creation_widget_case( imageDeBase = carreBleuA_OFF )

caseB = creation_widget_case( coordX = 300, imageDeBase = carreVertB_OFF )

caseC = creation_widget_case( coordY = 300, imageDeBase = carreRougeC_OFF )

caseD = creation_widget_case( coordX = 300, coordY = 300, imageDeBase = carreJauneD_OFF )


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

caseA.bind( '<Button-1>', lambda event : change(event,"A","ON") )

caseB.bind( '<Button-1>', lambda event : change(event,"B","ON") )

caseC.bind( '<Button-1>', lambda event : change(event,"C","ON") )

caseD.bind( '<Button-1>', lambda event : change(event,"D","ON") )


# Associe le relachement du bouton-gauche à une fonction-événement

caseA.bind( '<ButtonRelease-1>', lambda event : change(event,"A","OFF") )

caseB.bind( '<ButtonRelease-1>', lambda event : change(event,"B","OFF") )

caseC.bind( '<ButtonRelease-1>', lambda event : change(event,"C","OFF") )

caseD.bind( '<ButtonRelease-1>', lambda event : change(event,"D","OFF") )


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

D'ailleurs, on pourrait maintenant mettre les gestions d'événements directement dans la fonction de création. Il suffit de lui demander de fournir en plus la lettre qui caractérise le widget :

def creation_widget_case(lettre = "A", coordX = 100,coordY = 100,imageDeBase = creation_image_tk()) :

Ce qui permettra de faire des créations avec événements auto-gérés du type :

# Création et placement des widgets Label


caseA = creation_widget_case( lettre = "A", imageDeBase = carreBleuA_OFF )

caseB = creation_widget_case( lettre = "B", coordX = 300, imageDeBase = carreVertB_OFF )

caseC = creation_widget_case( lettre = "C", coordY = 300, imageDeBase = carreRougeC_OFF )

caseD = creation_widget_case( lettre = "D", coordX = 300, coordY = 300, imageDeBase = carreJauneD_OFF )


08° Faire les modifications proposées et modifier la fonction creation_widget_case pour qu'elle gère elle-même les événements sur le Label qu'elle est en train de créer.

La correction :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(lettre = "A", coordX = 100, coordY = 100, imageDeBase = creation_image_tk()) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Le paramètres imageDeBase correspond à la référence de l'image qu'on veut afficher initialement.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """

    widLabel = Label(fen_princ, image = imageDeBase)

    widLabel.place(x = coordX, y = coordY)

    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = carreBleuA_ON)

    elif action == "B ON" :

        caseB.configure(image = carreVertB_ON)

    elif action == "C ON" :

        caseC.configure(image = carreRougeC_ON)

    elif action == "D ON" :

        caseD.configure(image = carreJauneD_ON)

    elif action == "A OFF" :

        caseA.configure(image = carreBleuA_OFF)

    elif action == "B OFF" :

        caseB.configure(image = carreVertB_OFF)

    elif action == "C OFF" :

        caseC.configure(image = carreRougeC_OFF)

    elif action == "D OFF" :

        caseD.configure(image = carreJauneD_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = carreBleuA_ON)

        caseB.configure(image = carreVertB_ON)

        caseC.configure(image = carreRougeC_ON)

        caseD.configure(image = carreJauneD_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = carreBleuA_OFF)

        caseB.configure(image = carreVertB_OFF)

        caseC.configure(image = carreRougeC_OFF)

        caseD.configure(image = carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des images de base


carreBleuA_OFF = creation_image_tk(bleu = 200)

carreBleuA_ON = creation_image_tk(rouge = 75, vert = 75, bleu = 250)

carreVertB_OFF = creation_image_tk(vert = 200)

carreVertB_ON = creation_image_tk(rouge = 75, vert = 250, bleu = 75)

carreRougeC_OFF = creation_image_tk(rouge = 200)

carreRougeC_ON = creation_image_tk(rouge = 250,vert = 75,bleu = 75)

carreJauneD_OFF = creation_image_tk(rouge = 200, vert = 200)

carreJauneD_ON = creation_image_tk(rouge = 250, vert = 250)


# Création et placement des widgets Label


caseA = creation_widget_case( lettre = "A", imageDeBase = carreBleuA_OFF )

caseB = creation_widget_case( lettre = "B", coordX = 300, imageDeBase = carreVertB_OFF )

caseC = creation_widget_case( lettre = "C", coordY = 300, imageDeBase = carreRougeC_OFF )

caseD = creation_widget_case( lettre = "D", coordX = 300, coordY = 300, imageDeBase = carreJauneD_OFF )


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Pas mal non ? Nous avons déjà bien automatisé la création. Il va nous reste à automatiser la gestion de l'animation. Pour l'instant, quelques précisions sur les fonctions anonymes.

J'ai écrit une fonction avec un event car il s'agit d'une fonction-événement et qu'elle doit recevoir le event, anonyme ou pas.

Mais on peut très bien faire des fonctions lambda sans paramètre d'entrée :

>>> x = 5

>>> (lambda : x*x)()

25

Cela revient à taper :

>>> x = 5

>>> x*x

25

Les fonction lambda ont des limitations :

>>> x = 5

>>> y = (lambda : x*x)()

>>> y

>>> 25

>>> x = 5

>>> y = 2

>>> z = (lambda : x*y)()

>>> z

>>> 10

Les fonctions précédentes ne servent pas à grand chose. Par contre, on peut aussi fournir des arguments à une fonction lambda qui attend des paramètres :

>>> z = (lambda x,y: x*y)(5,2)

>>> z

>>> 10

Voici donc pour les fonctions lambda :

Fonctions lambda

On les utilise pour créer un référence vers une fonction à la volée.

Elles peuvent être créées sans paramètre :

>>> x = 5

>>> y = (lambda : x*x)()

>>> y

>>> 25

Elles peuvent être créée avec un ou plusieur paramètres :

>>> x = 5

>>> y = 2

>>> z = (lambda : x*y)()

>>> z

>>> 10

Elles peuvent renvoyer un code simulant un code de fonction sans paramètre à partir d'une fonction qui a elle des paramètres. On les utilise ainsi pour les événements qui ont besoin d'un simple nom de fonction, sans paramètre.

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

Dans le cas précédent, il ne faut pas oublier de préciser l'attente de l'argument event automatiquement envoyé.

4 - Attributs d'objets

Comment optimiser encore un peu plus ce code en terme de place ? Par exemple en créant et plaçant les deux images ON et OFF directement dans l'objet Label. Comme cela, nous n'aurons plus besoin de dire à chaque fois quelle image afficher depuis une référence extérieure.

Combien rajouter un attribut à un objet qui existe déjà ? C'est très facile :

monObjet.monNouvelAttribut = ceQueJeVeuxStocker

Du coup, on peut soit demander les couleurs en rajoutant des paramètres sur la fonction creation_widget_case, soit faire un test sur la valeur du paramètre lettre.

J'ai décidé de faire le premier choix. Du coup, je n'ai plus besoin du paramètre imageDeBase puisque je crée l'image directement à partir des intensités des couleurs.

def creation_widget_case(rouge = 0, vert = 0, bleu = 0, lettre = "A", coordX = 100, coordY = 100) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Les paramètres rouge,vert,bleu correspondent à la couleur initiale.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """


    # Création des deux images ON et OFF

    carreFonce = creation_image_tk(rouge = rouge, vert = vert, bleu = bleu)

    carreClair = creation_image_tk(rouge = rouge+50, vert = vert+50, bleu = bleu+50)


    # Création du Label

    widLabel = Label(fen_princ, image = carreFonce)

    widLabel.place(x = coordX, y = coordY)


    # Stockage de données dans le Label

    widLabel.image_OFF = carreFonce

    widLabel.image_ON = carreClair

    widLabel.lettreId = lettre


    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


09° Modifier le programme (qui ne fonctionne plus car j'ai intégré la nouvelle fonction creation_widget_case sans modifier le reste) pour qu'il fonctionne à nouveau. J'en ai profité pour stocker la lettre "A","B","C" ou "D" fournie lors de la création dans l'attribut lettreId. Nous l'utiliserons uniquement à la question suivante.

Pensez bien à modifier la fonction modifier_case qui pourra maintenant faire référence aux images stockées directement dans l'objet !

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(rouge = 0, vert = 0, bleu = 0, lettre = "A", coordX = 100, coordY = 100) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Les paramètres rouge,vert,bleu correspondent à la couleur initiale.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """


    # Création des deux images ON et OFF

    carreFonce = creation_image_tk(rouge = rouge, vert = vert, bleu = bleu)

    carreClair = creation_image_tk(rouge = rouge+50, vert = vert+50, bleu = bleu+50)


    # Création du Label

    widLabel = Label(fen_princ, image = carreFonce)

    widLabel.place(x = coordX, y = coordY)


    # Stockage de données dans le Label

    widLabel.image_OFF = carreFonce

    widLabel.image_ON = carreClair

    widLabel.lettreId = lettre


    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = carreBleuA_ON)

    elif action == "B ON" :

        caseB.configure(image = carreVertB_ON)

    elif action == "C ON" :

        caseC.configure(image = carreRougeC_ON)

    elif action == "D ON" :

        caseD.configure(image = carreJauneD_ON)

    elif action == "A OFF" :

        caseA.configure(image = carreBleuA_OFF)

    elif action == "B OFF" :

        caseB.configure(image = carreVertB_OFF)

    elif action == "C OFF" :

        caseC.configure(image = carreRougeC_OFF)

    elif action == "D OFF" :

        caseD.configure(image = carreJauneD_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = carreBleuA_ON)

        caseB.configure(image = carreVertB_ON)

        caseC.configure(image = carreRougeC_ON)

        caseD.configure(image = carreJauneD_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = carreBleuA_OFF)

        caseB.configure(image = carreVertB_OFF)

        caseC.configure(image = carreRougeC_OFF)

        caseD.configure(image = carreJauneD_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création des images de base


carreBleuA_OFF = creation_image_tk(bleu = 200)

carreBleuA_ON = creation_image_tk(rouge = 75, vert = 75, bleu = 250)

carreVertB_OFF = creation_image_tk(vert = 200)

carreVertB_ON = creation_image_tk(rouge = 75, vert = 250, bleu = 75)

carreRougeC_OFF = creation_image_tk(rouge = 200)

carreRougeC_ON = creation_image_tk(rouge = 250,vert = 75,bleu = 75)

carreJauneD_OFF = creation_image_tk(rouge = 200, vert = 200)

carreJauneD_ON = creation_image_tk(rouge = 250, vert = 250)


# Création et placement des widgets Label


caseA = creation_widget_case( lettre = "A", imageDeBase = carreBleuA_OFF )

caseB = creation_widget_case( lettre = "B", coordX = 300, imageDeBase = carreVertB_OFF )

caseC = creation_widget_case( lettre = "C", coordY = 300, imageDeBase = carreRougeC_OFF )

caseD = creation_widget_case( lettre = "D", coordX = 300, coordY = 300, imageDeBase = carreJauneD_OFF )


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


# Associe un clic-gauche sur un carré à une fonction-événement

fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Correction :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(rouge = 0, vert = 0, bleu = 0, lettre = "A", coordX = 100, coordY = 100) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Les paramètres rouge,vert,bleu correspondent à la couleur initiale.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """


    # Création des deux images ON et OFF

    carreFonce = creation_image_tk(rouge = rouge, vert = vert, bleu = bleu)

    carreClair = creation_image_tk(rouge = rouge+50, vert = vert+50, bleu = bleu+50)


    # Création du Label

    widLabel = Label(fen_princ, image = carreFonce)

    widLabel.place(x = coordX, y = coordY)


    # Stockage de données dans le Label

    widLabel.image_OFF = carreFonce

    widLabel.image_ON = carreClair

    widLabel.lettreId = lettre


    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = caseA.image_ON)

    elif action == "B ON" :

        caseB.configure(image = caseB.image_ON)

    elif action == "C ON" :

        caseC.configure(image = caseC.image_ON)

    elif action == "D ON" :

        caseD.configure(image = caseD.image_ON)

    elif action == "A OFF" :

        caseA.configure(image = caseA.image_OFF)

    elif action == "B OFF" :

        caseB.configure(image = caseB.image_OFF)

    elif action == "C OFF" :

        caseC.configure(image = caseC.image_OFF)

    elif action == "D OFF" :

        caseD.configure(image = caseD.image_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = caseA.image_ON)

        caseB.configure(image = caseB.image_ON)

        caseC.configure(image = caseC.image_ON)

        caseD.configure(image = caseD.image_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = caseA.image_OFF)

        caseB.configure(image = caseB.image_OFF)

        caseC.configure(image = caseC.image_OFF)

        caseD.configure(image = caseD.image_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création et placement des widgets Label


caseA = creation_widget_case( lettre = "A", bleu = 200 )

caseB = creation_widget_case( lettre = "B", coordX = 300, vert = 200 )

caseC = creation_widget_case( lettre = "C", coordY = 300, rouge = 200 )

caseD = creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 )


# Gestion des événements


fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Bon, c'est encore mieux qu'avant. Pour rappel, vous pourriez avoir besoin parfois de retrouver le widget sur lequel vous venez de cliquer. Il suffit alors d'utiliser le contenu de event : event.widget contient la référence du widget qui a généré l'alerte.

5 - Les listes

La dernière chose que nous allons cherché à automatiser ici, c'est ceci :

def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = caseA.image_ON)

    elif action == "B ON" :

        caseB.configure(image = caseB.image_ON)

    elif action == "C ON" :

        caseC.configure(image = caseC.image_ON)

    elif action == "D ON" :

        caseD.configure(image = caseD.image_ON)

    elif action == "A OFF" :

        caseA.configure(image = caseA.image_OFF)

    elif action == "B OFF" :

        caseB.configure(image = caseB.image_OFF)

    elif action == "C OFF" :

        caseC.configure(image = caseC.image_OFF)

    elif action == "D OFF" :

        caseD.configure(image = caseD.image_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = caseA.image_ON)

        caseB.configure(image = caseB.image_ON)

        caseC.configure(image = caseC.image_ON)

        caseD.configure(image = caseD.image_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = caseA.image_OFF)

        caseB.configure(image = caseB.image_OFF)

        caseC.configure(image = caseC.image_OFF)

        caseD.configure(image = caseD.image_OFF)

Comment ? En plaçant, à la création, toutes les références des Labels dans une liste. Disons listeDesCases.

10° Modifier le programme pour qu'il génére bien cette liste au fur et à mesure de la création des cases : il faudra stocker la référence fourni en retour de la fonction creation_widget_case dans la liste plutôt que dans les variables individuelles. Ne vous inquiétez pas de faire disparaitre les déclarations de caseA, caseB, caseC et caseD. Pour vérifier si vous avez bien travaillé, la création doit être opérationnelle mais pas les clics.

La partie à modifier est donc celle-ci :

caseA = creation_widget_case( lettre = "A", bleu = 200 )

caseB = creation_widget_case( lettre = "B", coordX = 300, vert = 200 )

caseC = creation_widget_case( lettre = "C", coordY = 300, rouge = 200 )

caseD = creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 )

Correction possible :

...CORRECTION...

# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesCases = []

"""

La variable listeDesCases contient la liste des Labels créés avec creation_widget_case.

"""


# Plein de code entre les deux


listeDesCases.append(creation_widget_case( lettre = "A", bleu = 200 ))

listeDesCases.append(creation_widget_case( lettre = "B", coordX = 300, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "C", coordY = 300, rouge = 200 ))

listeDesCases.append(creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 ))


Et maintenant, il va donc falloir modifier la fonction modifier_case. C'est la dernière zone du code qui utilise des références à caseA, caseB, caseC et caseD.

11° Adapter la fonction pour obtenir un code fonctionnel le plus court possible. Pour lire l'action et la découper en deux, vous pouvez par exemple utiliser le code suivant qui va placer dans deux variables le code case voulu et l'action ON ou OFF à faire. On utilise la méthode partition des strings qui renvoie un tuple de 3 éléments : la chaine avant le séparateur utilisé, le séparateur et la chaine après le séparateur.

codeCase, separateur, actionVoulue = action.partition(' ')

Si action = "A ON", on obtient :

A vous de jouer. Voici le code de base à modifier si vous n'avez pas réussi à réaliser la question précédente :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesCases = []

"""

La variable listeDesCases contient la liste des Labels créés avec creation_widget_case.

"""


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(rouge = 0, vert = 0, bleu = 0, lettre = "A", coordX = 100, coordY = 100) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Les paramètres rouge,vert,bleu correspondent à la couleur initiale.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """


    # Création des deux images ON et OFF

    carreFonce = creation_image_tk(rouge = rouge, vert = vert, bleu = bleu)

    carreClair = creation_image_tk(rouge = rouge+50, vert = vert+50, bleu = bleu+50)


    # Création du Label

    widLabel = Label(fen_princ, image = carreFonce)

    widLabel.place(x = coordX, y = coordY)


    # Stockage de données dans le Label

    widLabel.image_OFF = carreFonce

    widLabel.image_ON = carreClair

    widLabel.lettreId = lettre


    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    if action == "A ON" :

        caseA.configure(image = caseA.image_ON)

    elif action == "B ON" :

        caseB.configure(image = caseB.image_ON)

    elif action == "C ON" :

        caseC.configure(image = caseC.image_ON)

    elif action == "D ON" :

        caseD.configure(image = caseD.image_ON)

    elif action == "A OFF" :

        caseA.configure(image = caseA.image_OFF)

    elif action == "B OFF" :

        caseB.configure(image = caseB.image_OFF)

    elif action == "C OFF" :

        caseC.configure(image = caseC.image_OFF)

    elif action == "D OFF" :

        caseD.configure(image = caseD.image_OFF)

    elif action == "TOUTES ON" :

        caseA.configure(image = caseA.image_ON)

        caseB.configure(image = caseB.image_ON)

        caseC.configure(image = caseC.image_ON)

        caseD.configure(image = caseD.image_ON)

    elif action == "TOUTES OFF" :

        caseA.configure(image = caseA.image_OFF)

        caseB.configure(image = caseB.image_OFF)

        caseC.configure(image = caseC.image_OFF)

        caseD.configure(image = caseD.image_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création et placement des widgets Label


listeDesCases.append(creation_widget_case( lettre = "A", bleu = 200 ))

listeDesCases.append(creation_widget_case( lettre = "B", coordX = 300, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "C", coordY = 300, rouge = 200 ))

listeDesCases.append(creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 ))


# Gestion des événements


fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

...CORRECTION...

def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    codeCase, separateur, actionVoulue = action.partition(' ')

    for laCase in listeDesCases :

        if codeCase == "TOUTES" or codeCase == laCase.lettreId :

            if actionVoulue == "ON" :

                laCase.configure(image = laCase.image_ON)

            else :

                laCase.configure(image = laCase.image_OFF)

Voilà. Nous pourrions encore faire mieux en utilisant la programmation orientée objet mais cette partie sera traitée plus tard. Pour l'instant, nous allons utilisé notre nouveau code tout beau tout chaud pour réaliser une interface plus élaborée :

# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création et placement des widgets Label


listeDesCases.append(creation_widget_case( lettre = "A", bleu = 200, coordX = 0, coordY = 0 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 160, rouge = 40, coordX = 0, coordY = 100 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 120, rouge = 80, coordX = 100, coordY = 0 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 80, rouge = 120, coordX = 100, coordY = 100 ))

listeDesCases.append(creation_widget_case( lettre = "B", coordX = 300, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "C", coordY = 300, rouge = 200 ))

listeDesCases.append(creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "E", coordX = 200, coordY = 200, rouge = 50, vert = 50, bleu = 50 ))


# Gestion des événements


fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

12° Tester cette modification et réaliser ensuite un jeu adapté à vos envies. Vous pouvez donner d'autres identifiants que A,B,C et D d'ailleurs pour avoir autant de cases indépendantes que voulues.

Voici le code complet à modifier comme vous voulez :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


"""

DESCRIPTION :

L'utilisateur peut faire du clic-gauche sur les carrés colorés pour mémoriser une séquence.

Lorsqu'on utilise un clic-droit sur l'un des carrés, on montre la séquence qui a été enregistrée, dans l'ordre chronologique.

"""


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables du programme

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


listeDesCases = []

"""

La variable listeDesCases contient la liste des Labels créés avec creation_widget_case.

"""


listeDesActions = ['TOUTES ON','TOUTES OFF']

"""

La variable listeDesActions est une liste qui contient l'ensemble des actions que l'utilisateur a réalisé sur les carrés colorés.

Valeurs possibles dans cette liste :

'TOUTES ON' permet d'activer les 4 zones Images (les rendre plus claires)

'TOUTES OFF' permet de remettre les 4 carrés d'origine (les rendre plus foncées)

De la même façon :

'A ON' et 'A OFF' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B ON' et 'B OFF' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C ON' et 'C OFF' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D ON' et 'D OFF' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi si listeDesActions = ['D ON','D OFF','A ON','A OFF'], cela veut dire qu'on a mémorisé d'abord un clic sur le carré jaune D puis un clic sur le carré bleu A.

"""


animation = False # Variables GLOBAL pour certaines fonctions

"""

Cette variable sert à savoir si c'est le programme d'animation ou l'utilisateur qui a la main.

Elle doit valoir True pendant la réstitution de la séquence, l'utilisateur n'a pas la main.

Elle doit valoir False pendant la mémorisation de la séquence créée par l'utilisateur via les clics gauche.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions de CREATION

# - - - - - - - - - - - - - - - - - -


def creation_image_tk(rouge = 0, vert = 0, bleu = 0) :

    """

    Permet de créer une image PhotoImage compatible avec le module tkinter.

    Les paramètres rouge, vert et bleu sont des valeurs comprises entre 0 et 255 de la couleur RGB voulue.

    Renvoie la référence de l'image PhotoImage. Devra être stockée dans une variable.

    """

    carrePIL = Img.new("RGB", (100,100), (rouge,vert,bleu))

    carreTk = ImageTk.PhotoImage(carrePIL)

    return(carreTk)


def creation_widget_case(rouge = 0, vert = 0, bleu = 0, lettre = "A", coordX = 100, coordY = 100) :

    """

    Permet de créer un widget Label affichant une image.

    Les paramètres ccord_x et coordY sont les coordonnées du widget dans la fenêtre.

    Les paramètres rouge,vert,bleu correspondent à la couleur initiale.

    Renvoie la référence du widget Label. Devra être stockée dans une variable.

    """


    # Création des deux images ON et OFF

    carreFonce = creation_image_tk(rouge = rouge, vert = vert, bleu = bleu)

    carreClair = creation_image_tk(rouge = rouge+50, vert = vert+50, bleu = bleu+50)


    # Création du Label

    widLabel = Label(fen_princ, image = carreFonce)

    widLabel.place(x = coordX, y = coordY)


    # Stockage de données dans le Label

    widLabel.image_OFF = carreFonce

    widLabel.image_ON = carreClair

    widLabel.lettreId = lettre


    # Associe un clic-gauche sur un carré à une fonction-événement

    widLabel.bind( '<Button-1>', lambda event : change(event,lettre,"ON") )

    # Associe le relachement du bouton-gauche à une fonction-événement

    widLabel.bind( '<ButtonRelease-1>', lambda event : change(event,lettre,"OFF") )

    return(widLabel)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def animer() :

    """

    Cette fonction lance l'animation qui montre la séquence stockée dans listeDesActions.

    Elle prend l'action stockée dans listeDesActions[0].

    Elle change l'affichage en fonction de l'action.

    Elle supprime ensuite le premier élement de listeDesActions.

    Exemple :

    listesDesActions = ['A ON','A OFF','B ON','B OFF'] va gérer l'action "A ON".

    On supprime 'A ON' et on obtient

    listesDesActions = ['A OFF','B ON','B OFF'] va gérer l'action 'A ON'.

    """

    global animation


    # On place dans action l'action à faire, codée à l'aide d'un string : voir modifier_case

    action = listeDesActions[0]


    # PRINT POUR LE MODE DEBUG

    print(len(listeDesActions))

    print(listeDesActions)

    print(action)


    # On lance la modification à faire

    modifier_case(action)

    # On supprime l'action de la liste

    listeDesActions.remove(action)


    # Le test suivant permet de voir s'il reste des actions enregistrées ou si on a fini

    if len(listeDesActions) > 0 :

        fen_princ.after(500,animer)

    else :

        listeDesActions.append('TOUTES ON')

        listeDesActions.append('TOUTES OFF')

        animation = False


def modifier_case(action) :

    """

    Cette fonction permet de modifier les widgets en fonctin du parametre action.

    "A ON" active la 1er case, "A OFF" la désactive

    "B ON" active la 2e case, "B OFF" la désactive

    "C ON" active la 3e case, "C OFF" la désactive

    "D ON" active la 4e case, "D OFF" la désactive

    "TOUTES ON" active les 4 cases, "TOUTES OFF" les désactive

    """

    codeCase, separateur, actionVoulue = action.partition(' ')

    for laCase in listeDesCases :

        if codeCase == "TOUTES" or codeCase == laCase.lettreId :

            if actionVoulue == "ON" :

                laCase.configure(image = laCase.image_ON)

            else :

                laCase.configure(image = laCase.image_OFF)


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions EVENEMENTS

# - - - - - - - - - - - - - - - - - -


def change(event, case, etat ) :

    """

    Le paramètre case doit contenir le string "A","B","C" ou "D" par exemple.

    Le paramètre etat doit contenir le string "ON" ou "OFF" par exemple.

    """

    if animation == False :


        chaineGeneree = "{0} {1}".format(case,etat)

        # Cela revient à faire chaineGeneree = case+" "+etat

        modifier_case(chaineGeneree)

        listeDesActions.append(chaineGeneree)


def lancement(event) :

    """

    Fonction-événement qui lance l'animation de la séquence stockée.

    """

    global animation

    animation = True

    animer()


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


# Création et placement des widgets Label


listeDesCases.append(creation_widget_case( lettre = "A", bleu = 200, coordX = 0, coordY = 0 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 160, rouge = 40, coordX = 0, coordY = 100 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 120, rouge = 80, coordX = 100, coordY = 0 ))

listeDesCases.append(creation_widget_case( lettre = "A", bleu = 80, rouge = 120, coordX = 100, coordY = 100 ))

listeDesCases.append(creation_widget_case( lettre = "B", coordX = 300, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "C", coordY = 300, rouge = 200 ))

listeDesCases.append(creation_widget_case( lettre = "D", coordX = 300, coordY = 300, rouge = 200, vert = 200 ))

listeDesCases.append(creation_widget_case( lettre = "E", coordX = 200, coordY = 200, rouge = 50, vert = 50, bleu = 50 ))


# Gestion des événements


fen_princ.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()