Ma première interface utilisateur

Pour créer une interface utilisateur ( en anglais GUI ; Graphical User Interface), vous allez utiliser la bibliothèque Tkinter de python.

Pour cela créez un nouveau programme appelé Hello.py avec le code ci-dessous. Par ailleurs, vous devez travailler avec IDLE, car cela ajoutera automatiquement tkinter dans Python.

Vous enregistrerez chacun de vos programmes dans google Doc ( google drive) et vous le partagerez avec moi ( Cette adresse e-mail est protégée contre les robots spammeurs. Vous devez activer le JavaScript pour la visualiser. ).
Merci de bien présenter vos création sur google Doc en mettant le code avec des copies d'écran des fenêtres réalisées.

Remarque : Pour réaliser facilement des copies d'écrans, vous utiliserez l'outil capture d'écran de windows. Celui-ci se trouve dans l'onglet Accessoires windows du bouton fenêtre windows.  

 

from tkinter import *
from tkinter import ttk

 
   
def main():
    fen1 = Tk()

    label = ttk.Label( fen1, text="Hello World!" )
    label.pack()

    fen1.mainloop()

main()

 

 

  • Exécutez le programme. Soyez prudent, car cela va créer une petite fenêtre sur votre écran, ce qui peut être difficilement visible. Mais elle devrait être là!
  • Normalement, vous devriez voir afficher "Hello World!".

Explications sur le code : 

 Explications sur le programme

 1. Création de la fonction main() avec l'instruction def.
 2. Contenue de la fonction main()

Attention

Vous avez remarqué qu’à la deuxième ligne, on n’a pas commencé à écrire au début de la ligne. On dit qu’on fait une indentation. Et cette indentation est indispensable pour que Python fasse son travail. En règle générale, le bloc d’instructions (une ou plusieurs lignes) qui dépend d’une ligne (devant elle se terminer par :) doit être indenté. C’est obligatoire et en plus cela a l’avantage de rendre le script plus lisible.

3. Déclaration de la fenêtre
4. Déclaration du label (étiquette).
5. Déclaration du gestionnaire d'événements avec mainloop(). Cela permet à la fenêtre de gérer la souris, le claver, .. 
6. Appelle à la fonction main()

Exercice 1 :

QuestionMark2.jpg

 

  • Remplacez le texte du label apr "Bonjour le monde". 

 

Ajout d' un bouton

 

Maintenant, essayez d'ajouter un peu plus de code à votre application:

 

from tkinter import *
from tkinter import ttk
   
def main():
    fen1 = Tk()

    label = ttk.Label( fen1, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( fen1, text="Change Label" )
    button1.pack()
   
    fen1.mainloop()

main()

 

  • Exécutez le code. Normalement un nouveau bouton est apparu. Cliquez sur le bouton plusieurs fois. Pourquoi ne se passe t-il rien?
  • Si votre réponse est que nous n'avons pas définit une action pour le bouton, vous avez raison!
  • Ajoutons une nouvelle fonction au programme, et «attacher» au bouton.

 

from tkinter import *
from tkinter import ttk


def change():
    print( "la fonction change() est appelée" )
   
def main():
    global label
    fen1 = Tk()

    label = ttk.Label( fen1, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( fen1, text="Change Label", command=change )
    button1.pack()
   
    fen1.mainloop()


main()

 

  • Exécutez à nouveau votre code, et vérifiez que lorsque vous cliquez sur le bouton, quelque chose apparait dans la console.
  • La console n'est pas nécessaire pour des applications graphiques, mais, dans ce cas, elle est un bon moyen pour débuguer notre programme et vérifier qu'il fonctionne comme prévu.

Exercice 2: Ajouter un deuxième bouton

QuestionMark1.jpg
  • Ajouter un deuxième bouton à votre GUI. Appelez button2.
  • Ajouter une nouvelle fonction et faire le nouveau bouton activer cette fonction. Votre nouvelle fonction permet d'imprimer une chaîne de caractères simples sur la console. Assurez - vous que la chaîne et la fonction sont différentes de la fonction de changement déjà défini, et la chaîne qu'il imprime. 

 

from tkinter import *
from tkinter import ttk


def change():
    print( "la fonction change() est appelée" )
   
def main():
    global label
    fen1 = Tk()

    label = ttk.Label( fen1, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( fen1, text="Bouton 1", command=change )
    button1.pack()
   
button2 = ttk.Button( fen1, text="Bouton 2", command=change )
    button2.pack()

    fen1.mainloop()


main()

 

Bouton Changer le texte de l'étiquette

 

Réalisons un bouton qui permettra de modifier le texte du label quand vous cliquerez dessus:

 

from tkinter import *
from tkinter import ttk

label = None # this variable will hold the label created by the GUI, and will be accessible
                    # by the change1() function.

def change1():
    global label
    label.config( text = "Goodbye World!" )
   
def main():
    global label
    fen1 = Tk()

    label = ttk.Label( fen1, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( fen1, text="Bye!", command=change1 )
    button1.pack()
   
    fen1.mainloop()


main()

 

  • Exécutez votre code.
  • Vérifiez quand cliquant sur le bouton le contenu du label change.

 

Exercice 3:: Ajouter un deuxième bouton qui change le contenu du label

QuestionMark2.jpg

 

  • Ajoutez un deuxième bouton et modifiez le programme de sorte que le premier bouton change le label en "Goodbye World!", Et le second bouton le change en "Bonjour tout le monde!".
 

 

 

Mise en place Widgets dans une grille

 

  • plack() est un moyen très simple de mettre les widgets sur la fenêtre de l' interface graphique. Une option plus puissante est d'utiliser de grid(), ce qui nécessite que vous organisiez la position des widgets sur le papier avant de coder.
  • Mettons les trois widgets (label, button1 et button2) sur la même ligne. Cela correspond à une grille avec 1 ligne et 3 colonnes.
  • Seuls 3 lignes doivent changer. Ils sont indiqués ci-dessous:

 

    label.grid( row=0, column=0 )

    button1.grid( row=0, column=1 )

    button2.grid( row=0, column=2 )
 
  • Faire la modification et exécutez votre application. Est-ce que les 3 widgets s'affichent dans un alignement horizontal?

Exercice 4 : Réorganiser les Widgets

QuestionMark3.jpg

 

  • Changez l'organisation de la grille de sorte que le label se trouve entre les deux boutons.
  • Lorsque cela fonctionne, modifiez l'organisation de la grille de sorte que le label se trouve sur le côté droit de la fenêtre.
  • Changez l'organisation, de sorte que le label est seul sur une ligne, et que les deux boutons se trouvent l'un à coté de l'autre sur une ligne en dessous.

 

 

 

 

L' organisation de l'interface graphique en tant que classe

 

La nécessité de mettre le label en variable Global est lourde. Si vous avez de nombreux widgets, vous pourrez avoir à faire cela plusieurs fois. Il est préférable d'utiliser une classe pour la gestion des widgets, et de toutes les actions et événements qui leur sont associés.

 

from tkinter import *
from tkinter import ttk

class GUI:
    def __init__( self, fen1 ):
        self.label = ttk.Label( fen1, text="Hello World!" )
        self.label.grid( row=0, column=0 )

        self.button1 = ttk.Button( fen1, text="Hello",
                                   command=self.hello )
        self.button1.grid( row=0, column=1 )

        self.button2 = ttk.Button( fen1, text="Bye",
                                   command=self.bye )
        self.button2.grid( row=0, column=2 )
       
    def bye( self ):
        self.label.config( text = "Goodbye World!" )

    def hello( self ):
        self.label.config( text = "Hello World!" )
   
def main():
    global label
    fen1 = Tk()

    gui = GUI( fen1 )
    fen1.mainloop()


main()

 

Ajout d'un Checkbox ( case à cocher )

 

Un checkbox est un widget qui a une boîte que l'utilisateur peut cocher pour mettre en ON ou OFF.

Un checkbox doit être associé à un objet spécial dans le code. En effet, si l'utilisateur change la case en cliquant dessus, celui-ci modifiera automatiquement sa valeur. 

Cela semble compliqué. Un exemple va illustrer ce point.

Ajoutez une case à votre GUI. Mettez le code ci-dessous pour modifier votre interface graphique :

 

        # create an object to keep the value of the checkbox
        self.buttonsEnabled = IntVar()
        self.buttonsEnabled.set( 1 )

        # create a checkbox and associate it with the object above.
        self.check1 = ttk.Checkbutton( fen1, text="Enable", variable=self.buttonsEnabled )
        self.check1.grid( row=1, column=0 )
  • Notez que pour modifier la valeur de l'objet buttonsEnable, qui est instancié par IntVar. Vous utiliserez la méthode set(). Pour obtenir la valeur d'un tel objet, il faudra utiliser la méthode get().
  • Modifiez les fonctions bye() etbhello() de la classe de votre interface graphique, comme indiqué ci - dessous:

 

    def bye( self ):
        self.label.config( text = "Goodbye World!" )
        print( "buttonsEnabled = ", self.buttonsEnabled.get() )

    def hello( self ):
        self.label.config( text = "Hello World!" )
        self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
  • Cliquez sur le bouton hello plusieurs fois. Expliquez le comportement que vous observez. Si vous n'obtenez aucun changement, appelez le professeur.
  • Cliquez sur le bouton Bye  plusieurs fois, et regardez la console. Le code ci-dessus, et le comportement que vous observez doivent avoir un sens.
  • Enfin, cliquez sur le checkbox Enable  et uncheckes, puis cliquez sur le bouton approprié pour voir le contenu de l'objet self.buttonsEnabled.



Exercice 5: Contrôle des boutons avec le Checkbox

QuestionMark5.jpg

 

  • Le but du checkbox est de contrôler si les boutons peuvent modifier le label.
  • Nous voulons que les boutons puissent changer uniquement le label lorsque le checkbox est cochée, et qu'il ne changer pas le label lorsque celui-ci n'est pas cochée.
  • Modifiez votre classe GUI pour obtenir ce comportement.
 

Ajout d' un Text Area ( une zone de texte)

 

Un widget text area est une zone rectangulaire qui peut agir comme un simple éditeur de texte. En fait, l'éditeur de code que vous utilisez est probablement écrit en python avec la bibliothèque tkonter et son widget Text Area.

Avant d'ajouter la zone de texte, assurez-vous que vos 4 widgets (label, 2 boutons, checkbox) sont organisés dans une grille où ils sont tous alignés sur la ligne 0. Vous allez ajouter la zone de texte sur la ligne 1, et la quelle couvre les 4 colonnes.

Ajouter d'une zone de texte est très simple. Il faut mettre les de lignes de code suivantes à votre classe de GUI.

 

        self.text = Text( fen1, width=80, height=30, background="ivory" )
        self.text.grid( row=1, column=0, columnspan=4 )
  • Exécutez votre code. Vous devriez voir une fenêtre plus grande qu'auparavant. En effet, elle contient désormais une zone de texte qui peut contenir 30 lignes de 80 caractères.
  • Cliquez sur la zone de texte et entrez du texte à l'intérieur.

 

Effacement de la zone de texte

 

La zone de texte est entièrement documenté sur cette Page . Il est difficile de comprendre comment accéder aux différentes lignes. Mais heureusement, les instructions associés à l'effacement de la zone de texte sont simple à comprendr :

 

 self.text.delete(1.0, END)
 

Explications:

  • 1.0 signifie Ligne 1, colonne 0. La numérotation de Tkinter pour les lignes commence à 1 et 0 pour les colnnes.
  • END est une constante spéciale utilisée par Tkinter qui signifie la fin du texte dans la zone de texte. Donc , delete(1.0, END) effacera toute la zone de texte.


Exercice  7: bouton pour effacer la zone de texte

QuestionMark9.jpg

 

  • Modifiez votre classe GUI, et changer l'un des boutons, de sorte que son texte devient "Effecer", et que son action efface toute la zone de texte. Le bouton que vous choisirez ne doit pas être modifiable par le checkbox.

 

 

  

Ajout d' un Canevas

Dans la bibliothèque Tkinter, on ne peut pas dessiner ni insérer d'images en dehors d'un canevas.
Le canevas représente la surface pour dessiner ( = le container de notre dessin).
La ligne de commande peut être la suivante :

can = Canvas(fen,bg='black', height=200, width=300)
can.pack()

  • De nombreuses couleurs sont disponible : red, blue, yellow, white, black, green, gray, pink,orange mais aussi dark gray, dark red, magenta, turquoise, gold, ...
  • l’option bg='black' donne la couleur du fond de votre canevas.
  • can.pack() va ajuster notre fenêtre à la taille du canevas.
  • can est le nom de notre canvas, vous pouvez en changer à condition de garder ce même nom à chaque fois que vous y ferez référence.

Le canvas est alors muni d’un repère. Pour repérer vos points sur l'écran :

  • Le point en haut à gauche a pour coordonnées (0 ; 0);
  • le point en bas à droite par exemple dans une fenêtre de 200 * 300 sera le point(200 ; 300);
  • le point central a pour coordonnées : ( largeur de fenêtre /2 ; Hauteur de fenêtre / 2), etc.

Dessiner des lignes La ligne de commande peut être la suivante :

can.create_line(x1,y1,x2,y2,fill='red', width=5)

  • Cela permet de tracer une ligne sur notre canvas (can) du point (x1,y1) au point (x2,y2)
  • fill : pour préciser la couleur choisie
  • width : pour préciser la largeur de ligne.

Tous les paramètres sont facultatifs, ils doivent être séparés par des virgules. Par défaut, le tracé se fera en noir sans remplissage.
Vous pouvez créer plusieurs lignes en une seule ligne de commande donnant d'autres points de coordonnées (x,y). Par exemple :

can.create_line(x1,y1,x2,y2,x3,y3)

Dessiner du texte

can.create_text(x=200, y=200, text="mon texte ici", font="Arial 12",fill="cyan")

  • x,y : précise le centre du texte à écrire;
  • font : précise la police choisie;
  • fill : précise la couleur de l'écriture;

Dessiner un rectangle

can.create_rectangle(x1,y1,x2,y2, outline="red ", fill="pink ")

  • (x1,y1) représente le point en haut à gauche;
  • (x2,y2) le point en bas à droite;
  • outline =couleur du tour du tracé entre guillemets,
  • fill = la couleur de remplissage entre guillemets.

Dessiner une ellipse

can.create_oval(x1,y1,x2,y2,fill='blue', outline='green')

  • (x1,y1,x2,y2) coordonnées du rectangle circonscrit (qui contient l'ellipse)

Donc pour tracer un cercle vous aurez:

can.create_oval(x-rayon,y-rayon,x+rayon,y+rayon,fill='orange', outline='gold')

Dessiner un polygone

C'est comme des tracés de lignes, mais le dernier tracé viendra rejoindre le premier par une dernière ligne :

can.create_polygon(x1,y1,x2,y2,x3,y3)

option possible : smooth=True #pour arrondir les lignes.

Exécutez le script ci-dessous :

from tkinter import*
from random import randrange

# --- définition des fonctions gestionnaires d'événements : ---
def drawline(): #Tracé d'une ligne dans le canevas can1
global x1, y1, x2, y2, coul # Nouveau !
can1.create_line(x1,y1,x2,y2,width=2,fill=coul)
# modification des coordonnées pour la ligne suivante :
y2, y1 = y2+10, y1-10

def changecolor():
#Changement aléatoire de la couleur du tracé
global coul
pal=['purple','cyan','maroon','green','red','blue','orange','yellow']
c = randrange(8) # => génère un nombre aléatoire de 0 à 7
coul = pal[c]

#------ Programme principal ------
# Les variables suivantes seront utilisées de manière globale :
x1, y1, x2, y2 = 10, 190, 190, 10 # coordonnées de la ligne
coul = 'dark green' # couleur de la ligne
# Création du widget principal ("maître") :
fen1 = Tk()
# Création des widgets "esclaves" :
can1 = Canvas(fen1,bg='dark grey',height=200,width=200)
can1.pack(side=LEFT)
bou1 = Button(fen1,text='Quitter',command=fen1.destroy)
bou1.pack(side=BOTTOM)
bou2 = Button(fen1,text='Tracer une ligne',
command=drawline)
bou2.pack()
bou3 = Button(fen1,text='Autre couleur',command=changecolor)
bou3.pack()
fen1.mainloop()
 

A retenir ! La commande Global : permet lors de l’appel d’une fonction d’utiliser et surtout de modifier des variables créées dans le programme principal faisant appel à cette fonction. 

 

Exercice 8 :

QuestionMark2.jpg

 Vous allez avoir à modifier le programme précédent.
Après chaque modification, copier sur google drive les différents programmes.
1. Modifier le programme pour ne plus avoir que des lignes de couleur cyan, maroon et green.
2. Modifier le programme pour que toutes les lignes tracées soient horizontales et parallèles.

3. Ajouter une fonction drawline2( ) qui tracera deux ligne rouges en croix au centre du canevas : l'une horizontale et l'autre verticale. Ajouter également un bouton portant l'indication « viseur ». Un clic sur ce bouton devra provoquer l'affichage de la croix.

 

4. Reprendre le programme initial. Remplacer la méthode create_line par la méthode create_rectangle. Que se passe-t-il ? Qu' indique les coordonnées fournies en paramètres ?

Appeler le professeur pour vérification.

 

Pour dessiner un carré à l'écran, on utilisera ces outils. Mais pour créer nos personnages de jeux, nous allons gagner du temps et de la précision en utilisant des outils de dessins (Photoshop, Gimp, ...) pour ensuite simplement importer ces images dans notre canevas.

Pour tout effacer dans un canevas? La commande

can.delete(ALL)

Insérer une image dans le canevas

Tkinter ne permet pas d'insérer n'importe quelles types d'images. La principale extension acceptée est .gif.
Nous allons donc créer ou transformer toutes nos images en GIF afin de pouvoir les utiliser.

La bonne nouvelle c'est que vous pouvez utiliser des images transparentes au format GIF.
Apprenez à créer des images transparentes (c'est à dire dont le fond se confondra avec le fond de votre fenêtre quelle que soit sa couleur), pour cela il vous faudra préciser ou cocher "fond transparent" dans votre outil de dessin préféré quand vous créerez vos images.

Maria de Nintendo

Source : https://media2.giphy.com/media/nOoM9YZ2QuYQE/200_s.gif

fichier_img=PhotoImage(file='mon_image.gif') #pour charger l'image depuis un fichier
mnImage = can1.create_image(x,y,image=fichier_img) # pour copier l'image sur le canevas

  • x ,y coordonnées du centre de l'image

Exercice  9

QuestionMark9.jpg

 

  • Ecrire un script canevas_image.py contenant un canevas de taille 1000 x 1000, de fond jaune, avec au centre de ce canevas l’image 200_s.gif (que vous devez télécharger et mettre dans le même répertoire que votre programme python).

 

 

from tkinter import*

fen = Tk()

#Création du cadre
cadre = Canvas(fen, width=1000, height=1000, bg="yellow")
cadre.pack()

fichier_img=PhotoImage(file='200_s.gif') #pour charger l'image depuis un fichier
mario = cadre.create_image(500,500,image=fichier_img) # pour copier l'image sur le canevas

fen.mainloop()

Détection et positionnement d'un clic de souris

Copier puis exécuter le programme est le suivant :

# Détection et positionnement d'un clic de souris dans une fenêtre :
from tkinter import*

from tkinter import*

#Déclaration de la fonction position pour afficher les cordonnées du clic souris
def position(event):
chaine.configure(text="Clic détecté en X = " + str(event.x) + ", Y = " + str(event.y))

fen = Tk()

#Création du cadre
cadre = Canvas(fen, width=200, height=150, bg="yellow")
#gestion de l'événement clic souris
cadre.bind("<Button-1>", position)
cadre.pack()

chaine = Label(fen, text="VIDE")
chaine.pack()

fen.mainloop()

Le script fait apparaître une fenêtre contenant un cadre (frame) rectangulaire de couleur jaune pâle.
La méthode bind( ) du widget cadre associe l'événement au gestionnaire d'événement « pointeur ».
Ce gestionnaire d'événement peut utiliser les attributs x et y de l'objet event généré automatiquement par Python, pour construire la chaîne de caractères qui affichera la position de la souris au moment du clic. Exercice : Modifier le script ci-dessus de manière à faire apparaître un petit cercle rouge à l'endroit où l'utilisateur a effectué son clic (il faut d'abord remplacer le widget Frame par un widget Canvas).

 

Animation: déplacer un objet

Tkinter vous offre la possibilité de déplacer un objet (une image ou un dessin fait à la main) par la fonction :

can.coords(nom_objet,x1,y1,x2,y2) # efface et redessine l'objet

  • nom_objet désigne le nom de l'objet à déplacer,
  • x1,y1,x2,y2 sont les nouvelles coordonnées de l'objet, cette instruction est magique : elle efface l'objet et le recrée plus loin, sans qu'on ait à écrire ces deux étapes.

Exemple : Copier et exécuter le programment suivant :

from tkinter import*

# Procédure générale de déplacement :
def avance(gd, hb):
global x1, y1
x1 = x1+gd
y1 = y1+hb
can1.coords(oval1, x1, y1, x1+30, y1+30)

# Gestionnaire d'événements :
def depl_gauche():
avance(-10, 0)
def depl_droite():
avance(10, 0)
def depl_haut():
avance(0, -10)
def depl_bas():
avance(0, 10)

##### Programme principal #####
# Les variables suivantes seront utilisées de manière globale :
x1 = 10
y1 = 10 # coordonnées initiales
# Création du widget "maître" :
fen1 = Tk()
fen1.title("Exercice d'animation avec Tkinter")
# Création des widgets "esclaves":
can1 = Canvas(fen1, bg='dark gray', height=300, width=300)
oval1=can1.create_oval(x1,y1,x1+30,y1+30,width=2,fill='red')
can1.pack(side=LEFT)
Button(fen1,text='Quitter',command=fen1.destroy).pack(side=BOTTOM)
Button(fen1,text='Gauche',command=depl_gauche).pack()
Button(fen1,text='Droite',command=depl_droite).pack()
Button(fen1,text='Haut',command=depl_haut).pack()
Button(fen1,text='Bas',command=depl_bas).pack()

# Démarrage du réceptionnaire d'événement :
fen1.mainloop()

La fonction avance( ) redéfinit les coordonnées de l'objet « cercle coloré » (oval1) à chaque fois que l'on clique sur un des boutons.
Ce qui provoque son animation.

Remarque ! Les boutons ont été définis de manière plus compact (pas d'utilisation de variables).

 

Exercice 10:.

QuestionMark2.jpg

 Modifier le programme précédent de manière à ce que le cercle oval1 se place à l'endroit où l'on clique avec la souris

 

Notion de fonction récursive - animation avec la commande after()

Exercice 11 : Copier et exécuter le programme suivant puis compléter les lignes de commentaires :

from tkinter import *

def anime():
# ……………………………………………………………….. :
global x, y
if x<=250:
x=x+1
y=y+1
# .............................................................................................................................. :
canevas.coords(bille,x,y, x+50,y+50)
#............................................................................................................................... :
fen.after(10, anime)

#### programme principal ####
fen=Tk()
fen.title('animation avec tkinter')
# ………………………………………………………………………………………….. :
canevas=Canvas(fen,bg='dark gray',height=300, width=300)
canevas.pack()
x,y=0,0
# ……………………………………………………………………………………………………………………:
bille= canevas.create_oval(x,y,x+50,y+50,fill='orange')
anime()
fen.mainloop()

L'instruction fen.after(10, anime) redéclenche la fonction anime au bout de 10 millisecondes, comme cette instruction se trouve à la fin de la fonction anime(), elle va se rappeler elle-même, on l'appelle une fonction récursive (fonction qui s'appelle elle-même). On obtient ainsi l'animation attendue avec un affichage toutes les 10 ms.

D’autres instructions sont possibles avec Tkinter comme la détection du clavier, l’ouverture de boîtes de dialogue ou encore la gestion du temps. Vous trouverez des compléments de cours aux liens suivants :

http://www.jchr.be/python/tkinter.htm#image
http://fsincere.free.fr/isn/python/cours_python_tkinter.php
http://softdmi.free.fr/tuto_python/tuto-python.php?chapitre=2&page=1
http://www.cgmaths.fr/Atelier/Programmation/InterfacesGraphiques.pdf
http://python.developpez.com/cours/TutoSwinnen/?page=Chapitre8

Sources : 
https://maths.ac-noumea.nc/IMG/pdf/tp2_tkinter.pdf
http://www.python.org
http://cs.smith.edu/dftwiki/index.php/Tutorial:TKInter_Lab
http://tkinter.fdex.eu/doc/caw.html

 

En poursuivant votre navigation sur mon site, vous acceptez l’utilisation des Cookies et autres traceurs  pour réaliser des statistiques de visites et enregistrer sur votre machine vos activités pédagogiques. En savoir plus.