Formation I.S.N.

Événements - Exercices supplémentaires

Plus tard ! Les exercices de cette page ne sont pas à réaliser le jour de la formation (sauf s'il vous reste du temps bien évidemment). Ils sont là pour vous donner d'autres idées d'exercices, approfondir vos connaissances ou vous entraîner si besoin.

Logiciel de dessin

Vous allez réaliser votre premier logiciel de dessin !
L'idée est de dessiner une ligne lorsque le bouton de la souris reste enfoncé. Voici ce que vous devrez obtenir :


##----- Importation des Modules -----##
from tkinter import *

##----- Définition des Variables globales -----##
x1 = 0				# Mémorisation de la position antérieure de la souris 
y1 = 0 
l, h = 480, 320		# Largeur et hauteur du canevas


##----- Définition des Fonctions -----##
def clic(event):
    """ Prise en compte de l'événement clic gauche sur la zone graphique """
    # Ecrivez votre code ici


def deplace_clic(event):
    """ Prise en compte de l'événement déplacement de la souris avec clic gauche enfoncé
        jusqu'au relâchement du clic gauche."""
    # Ecrivez votre code ici


def effacer():
    """ Efface la zone graphique """
    zone_dessin.delete(ALL)


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Dessin')

##----- Création des boutons -----##
bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row=1, column = 1, padx = 5, pady = 5)
bouton_effacer = Button(fen, text='Effacer', command=effacer)
bouton_effacer.grid(row=1, column = 0, padx = 5, pady = 5)

##----- Création du canevas -----##
zone_dessin = Canvas(fen, width = l, height = h, bg ='white')
zone_dessin.grid(row=0, column = 0, columnspan = 2, padx =5, pady =5)

##----- Programme principal -----##

fen.mainloop()                  # Boucle d'attente des événements
				
  • Une piste pour dessiner
  • Une piste pour l'événement
  • Une solution

En fait, on ne trace que des « micro-lignes » dans le canevas. Pour dessiner, il faut utiliser la méthode .create_line(x1, y1, x2, y2) de où $M_1$(x1 ; y1) et $M_2$(x2 ; y2) sont les deux extrémités du segment tracé.

Il faudra donc mémoriser à la fois la position actuelle de la souris et sa position antérieure afin de tracer le segment entre les deux positions. Recourir à des variables globales semble alors nécessaire...

Voici les événements à prendre en compte :

  • <Button-1> identifie le clic gauche de la souris.
  • <B1-Motion> identifie le mouvement de la souris alors que le bouton gauche est enfoncé, jusqu'au relâchement de ce bouton.

##----- Importation des Modules -----##
from tkinter import *

##----- Définition des Variables globales -----##
x1 = 0				# Mémorisation de la position antérieure de la souris 
y1 = 0 
l, h = 480, 320		# Largeur et hauteur du canevas


##----- Définition des Fonctions -----##
def clic(event):
    """ Prise en compte de l'événement clic gauche sur la zone graphique """
    global x1, y1
    # position du clic
    x1 = event.x
    y1 = event.y


def deplace_clic(event):
    """ Prise en compte de l'événement déplacement de la souris avec clic gauche enfoncé
        jusqu'au relâchement du clic gauche."""
    global x1, y1
    # nouvelle position après déplacement de la souris - bouton enfoncé
    x2 = event.x
    y2 = event.y
    # on dessine une ligne
    zone_dessin.create_line(x1, y1, x2, y2, fill='green')
    x1 = x2
    y1 = y2


def effacer():
    """ Efface la zone graphique """
    zone_dessin.delete(ALL)


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Dessin')

##----- Création des boutons -----##
bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row=1, column = 1, padx = 5, pady = 5)
bouton_effacer = Button(fen, text='Effacer', command=effacer)
bouton_effacer.grid(row=1, column = 0, padx = 5, pady = 5)

##----- Création du canevas -----##
zone_dessin = Canvas(fen, width = l, height = h, bg ='white')
zone_dessin.grid(row=0, column = 0, columnspan = 2, padx =5, pady =5)

##----- Programme principal -----##
zone_dessin.bind('<Button-1>', clic)            # pour le premier clic
zone_dessin.bind('<B1-Motion>', deplace_clic)   # pour le déplacement bouton enfoncé

fen.mainloop()                  # Boucle d'attente des événements
						

Cercle déplaçable et drapeau

On repart d'une fenêtre contenant un canevas, une zone de texte et un bouton [Quitter].


##----- Importation des Modules -----##
from tkinter import *

##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Déplacer un cercle')

##----- Création des boutons -----##
bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row = 1, column = 1, sticky=S+W+E, padx=25, pady=5)

##----- Création d'une zone de texte -----##
message = Label(fen, text='Texte à modifier.')
message.grid(row = 1, column = 0, sticky = W+E)

##----- Création du canevas -----##
dessin=Canvas(fen, bg='white', width=300, height=300)
dessin.grid(row = 0, column = 0, columnspan = 2, padx=5, pady=5)

##----- Programme principal -----##
fen.mainloop()                  # Boucle d'attente des événements
				

A partir de cette interface de référence, concevoir un script qui affiche un cercle rouge à l'emplacement du clic de la souris et qui permet à l'utilisateur de déplacer ce cercle tant que le bouton de la souris n'a pas été relâché. Ce script nécessite de définir trois fonctions :

  • La fonction cliquer() se déclenche au clic de la souris. Elle doit tracer un cercle rouge sous la souris et «lève» un drapeau (valeur True) autorisant le déplacement du cercle.
  • La fonction lacher() se déclenche lorsque le bouton gauche de la souris n'est plus enfoncé. Cette fonction abaisse le drapeau (valeur False) et n'autorise plus le déplacement du cercle.
  • La fonction deplacer() qui se déclenche lorsque la souris se déplace au-dessus du canevas et lorsque le drapeau autorisant le déplacement est levé (valeur True). Cette fonction redéfinit les coordonnées du cercle.
  • Une solution

##----- Importation des Modules -----##
from tkinter import *

##----- Définition des Variables globales -----##
cercle = 0          					# D'abord 0 avant d'être une figure
drapeau = False     					# Variable globale

##----- Définition des Fonctions -----##
def cliquer(event):
    """ Entrées : Un événement - ici un clic gauche de souris
		Sorties  : Trace un cercle rouge sous la souris dans le Canvas."""
    global drapeau, cercle
    drapeau = True              # On lève le drapeau
    abscisse = event.x          # Abscisse de la souris
    ordonnee = event.y          # Ordonnée de la souris
    cercle = dessin.create_oval(abscisse-10, ordonnee-10, abscisse+10, ordonnee+10,width=2,outline='red')


def lacher(event):
    """ Entrées : Un événement - ici un bouton (gauche) de la souris relâché
		Sorties : Relâche le cercle."""
    global drapeau
    drapeau = False             # On baisse le drapeau      


def deplacer(event):
    """ Entrées : Un événement - ici un bouton (gauche) de la souris enfoncé
		Sorties : Conserve le cercle rouge sous la souris tant que..."""
    global drapeau, cercle
    if drapeau:                 # Si le drapeau est levé
        abscisse = event.x      # Abscisse de la souris
        ordonnee = event.y      # Ordonnée de la souris
        # Re-définition des coordonnées du cercle :
        dessin.coords(cercle, abscisse-10, ordonnee-10, abscisse+10, ordonnee+10)
        message.configure(text = 'Souris en X = {0} et Y = {1}'.format(abscisse, ordonnee))

##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Déplacer un cercle')

##----- Création des boutons -----##
bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row = 1, column = 1, sticky=S+W+E, padx=25, pady=5)

##----- Création d'une zone de texte -----##
message = Label(fen, text='Texte à modifier.')
message.grid(row = 1, column = 0, sticky = W+E)

##----- Création du canevas -----##
dessin=Canvas(fen, bg='white', width=300, height=300)
dessin.grid(row = 0, column = 0, columnspan = 2, padx=5, pady=5)

##----- Programme principal -----##
dessin.bind('<Button-1>', cliquer)
dessin.bind('<ButtonRelease-1>', lacher)
dessin.bind('<Motion>', deplacer)

fen.mainloop()                  # Boucle d'attente des événements
						

Retourner une carte

Vous devez tout d'abord télécharger les deux images ci-dessous (carte1.gif et carte2.gif) et les enregistrer dans le même dossier que celui de votre programme Python.

carte1.gif carte2.gif

A partir du code ci-dessous, vous devez établir un programme qui affiche une carte dans un canevas. Un clic gauche avec la souris sur la carte retourne celle-ci.


##----- Importation des Modules -----##
from tkinter import *

##----- Définition des Fonctions -----##
def changeCarte(event):
    """Changement de carte si clic sur la carte"""
    # Ecrivez votre code ici  

##----- Création de la fenêtre -----##
maFenetre=Tk()
maFenetre.title("Jeu de carte")

##----- Création des boutons -----##
btn = Button(maFenetre, text="Quitter", command=maFenetre.destroy)
btn.grid(row=1, column=0, padx=5, pady=5)

##----- Création du canevas -----##
can1 = Canvas(maFenetre, width =240, height =240, bg ='white')
can1.grid(row=0, column=0, padx=5, pady=5)

##----- Items du canevas -----##
# Ecrire ici le code de placement dans le canevas de l'image initiale "maCarte"

##----- Événement lié au canevas -----##
can1.bind("<Button-1>", changeCarte)

##----- Programme principal -----##
maFenetre.mainloop()                  # Boucle d'attente des événements
				
  • Affichage d'une image .gif
  • Retournement de la carte
  • Une solution

L'affichage d'une image .gif dans un canevas (identifié par can1) se fait simplement à l'aide de ces deux lignes :


imageGif = PhotoImage(file="carte1.gif", master = maFenetre)
maCarte = can1.create_image((50, 10), image =imageGif, anchor="nw")
							

La première ligne charge l'image en mémoire dans l'objet imageGif, et la seconde se charge de l'affichage dans le canevas aux coordonnées indiquées. L'attribut anchor sert à indiquer que les coordonnées données sont celles du coin supérieur gauche.

Dans le reste du programme, l'objet image dans le canevas sera accessible via la variable maCarte.

Pour retourner la carte, on doit reconfigurer l'objet maCarte du canevas afin de modifier son attribut image sur l'autre face de l'image. Cela se fait grâce à la méthode .itemconfigure() du canevas :


can1.itemconfigure(maCarte, image=imageGif)
							

Le reste de la fonction changeCarte() est assez explicite : il s'agit de charger alternativement l'image carte1.gif ou carte2.gif. On utilise pour cela une petite astuce utilisant l'opération modulo :


noImage = noImage%2 + 1				# echange du n° de carte
imageGif=PhotoImage(file="carte"+str(noImage)+".gif", master = maFenetre)
							

Pour finir, on s'assure de ne retourner la carte que si le clic a été fait dans la zone de la carte. Comme on connaît les coordonnées du clic, un bête test se charge de cette vérification.


##----- Importation des Modules -----##
from tkinter import *

##----- Variables globales -----##
noImage = 1							# No de la carte

##----- Définition des Fonctions -----##
def changeCarte(event):
    """Changement de carte si clic sur la carte"""
    global noImage,imageGif
    # On verifie que le clic a eu lieu sur la carte
    if event.x>50 and event.x<195 and event.y>10 and event.y<210:
        noImage = noImage%2+1										# echange du no de carte
        imageGif = PhotoImage(file="carte"+str(noImage)+".gif", master = maFenetre)
        can1.itemconfigure(maCarte, image=imageGif)					# On charge la nouvelle image

##----- Création de la fenêtre -----##
maFenetre=Tk()
maFenetre.title("Jeu de carte")

##----- Création des boutons -----##
btn = Button(maFenetre, text="Quitter", command=maFenetre.destroy)
btn.grid(row=1, column=0, padx=5, pady=5)

##----- Création du canevas -----##
can1 = Canvas(maFenetre, width =240, height =240, bg ='white')
can1.grid(row=0, column=0, padx=5, pady=5)

##----- Items du canevas -----##
imageGif = PhotoImage(file="carte1.gif", master = maFenetre)
maCarte = can1.create_image((50, 10), image =imageGif,anchor="nw" )

##----- Événement lié au canevas -----##
can1.bind("<Button-1>", changeCarte)

##----- Programme principal -----##
maFenetre.mainloop()                  # Boucle d'attente des événements
						

Boutons et souris pilotent le même événement

L'interface graphique du programme ci-dessous contient un canevas et plusieurs boutons.
Un disque de rayon 20 pixels est, au départ, tracé au centre du canevas.


##----- Importation des Modules -----##
from tkinter import *


##-----Définition des Variables globales-----##
x0, y0 = 150, 150           # Coordonnées initiales du centre du disque
x1, y1 = x0, y0             # Coordonnées actuelle du centre du disque


##----- Définition des Fonctions -----##
def depl_gauche():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers la gauche."""
    

def depl_droite():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers la droite."""
    

def depl_haut():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers le haut."""
    

def depl_bas():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers le bas."""
    

def reinit():
    """Cette fonction ré-initialise l'interface graphique."""


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Animer un cercle')


##----- Création des boutons -----##
bouton_haut = Button(fen, text='Haut', width=10, command=depl_haut)
bouton_haut.grid(row = 1, column = 0, columnspan=2, padx=3, pady=3)

bouton_bas = Button(fen, text='Bas', width=10, command=depl_bas)
bouton_bas.grid(row = 3, column = 0, columnspan=2, padx=3, pady=3)

bouton_gauche = Button(fen, text='Gauche', width=10, command=depl_gauche)
bouton_gauche.grid(row = 2, column = 0, padx=3, pady=3)

bouton_droit = Button(fen, text='Droite', width=10, command=depl_droite)
bouton_droit.grid(row = 2, column = 1,padx=3, pady=3)

bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row = 3, column = 3, sticky=S+W+E, padx=25, pady=5)

bouton_reload = Button(fen, text='Recommencer', width=12, command=reinit)
bouton_reload.grid(row = 2, padx=25, pady=5)


##----- Création du canevas -----##
dessin=Canvas(fen, bg='white', width=300, height=300)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=5, pady=5)


##----- Programme principal -----##
cercle = dessin.create_oval(x1-15, y1-15, x1+15, y1+15, width=2, fill='red')

fen.mainloop()                  # Boucle d'attente des événements
				
  1. Compléter les fonctions associées aux boutons : haut(), gauche(), droite(), bas() et reinit().
    Les quatre premières fonctions sont sur le même modèle : elles effectueront un décalage des coordonnées du disque de d pixels, où d est un entier choisi arbitrairement. Ces quatre fonctions peuvent donc toutes faire appel à une même fonction générique de déplacement.
  2. Il serait agréable que le disque puisse aussi être déplacé en fonction des clics de souris sur le canevas. Pour cela, il faut «partager» ce canevas en quatre zones, visualisées ci-contre (mais qui ne seront pas affichées par l'interface graphique) à l'aide de deux droites vertes passant par le centre du disque.
    Déterminer les équations de ces deux droites en fonction des coordonnées x1 et y1 du centre du disque.
  3. Programmer...
  • Une solution

##----- Importation des Modules -----##
from tkinter import *


##-----Définition des Variables globales-----##
x0, y0 = 150, 150           # Coordonnées initiales du centre du disque
x1, y1 = x0, y0             # Coordonnées actuelle du centre du disque


##----- Définition des Fonctions -----##
def avance(dx, dy):
    """Cette fonction redéfinit les coordonnées du
    centre du disque pour simuler un déplacement."""
    global x1, y1, cercle
    x1, y1 = x1+dx, y1+dy
    dessin.coords(cercle, x1-15, y1-15, x1+15, y1+15)

def depl_gauche():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers la gauche."""
    avance(-10, 0)

def depl_droite():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers la droite."""
    avance(10, 0)

def depl_haut():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers le haut."""
    avance(0, -10)

def depl_bas():
    """Cette fonction "déplace" le centre du disque
    de 10 pixels vers le bas."""
    avance(0, 10)

def reinit():
    """Cette fonction ré-initialise l'interface graphique."""
    global x1, y1, cercle
    x1, y1 = x0, y0
    dessin.coords(cercle, x1-15, y1-15, x1+15, y1+15)

def pointeur(event):
    """Cette fonction "déplace" le centre du disque selon
    la position du clic de souris autour de lui."""
    abscisse = event.x
    ordonnee = event.y
    k = -abscisse+x1+y1         # Equation de la 1ere bissectrice
    p = abscisse-x1+y1          # Equation de la 2nde bissectrice
    if (ordonnee < k) and (ordonnee < p):
        depl_haut()
    elif (ordonnee >= k) and (ordonnee >= p):
        depl_bas()
    elif (ordonnee <= k) and (ordonnee >= p):
        depl_gauche()
    else:
        depl_droite()


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Animer un cercle')


##----- Création des boutons -----##
bouton_haut = Button(fen, text='Haut', width=10, command=depl_haut)
bouton_haut.grid(row = 1, column = 0, columnspan=2, padx=3, pady=3)

bouton_bas = Button(fen, text='Bas', width=10, command=depl_bas)
bouton_bas.grid(row = 3, column = 0, columnspan=2, padx=3, pady=3)

bouton_gauche = Button(fen, text='Gauche', width=10, command=depl_gauche)
bouton_gauche.grid(row = 2, column = 0, padx=3, pady=3)

bouton_droit = Button(fen, text='Droite', width=10, command=depl_droite)
bouton_droit.grid(row = 2, column = 1,padx=3, pady=3)

bouton_quitter = Button(fen, text='Quitter', command=fen.destroy)
bouton_quitter.grid(row = 3, column = 2, sticky=S+W+E, padx=25, pady=5)

bouton_reload = Button(fen, text='Recommencer', width=12, command=reinit)
bouton_reload.grid(row = 2, column = 2, padx=25, pady=5)


##----- Création du canevas -----##
dessin=Canvas(fen, bg='white', width=300, height=300)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=5, pady=5)


##----- Programme principal -----##
cercle = dessin.create_oval(x1-15, y1-15, x1+15, y1+15, width=2, fill='red')
dessin.bind('<Button-1>', pointeur)

fen.mainloop()                  # Boucle d'attente des événements