Infoforall

Python 16 : Interface Tk - GESTION DES EVENEMENTS

Cette partie vous montrera comment parvenir à attacher certains événements (dont l'appui sur les touches) à l'appel d'une fonction.

Nous allons par exemple diriger pacman à l'aide des fléches ou des touches plutôt qu'à l'aide de widgets-Button.

L'appui sur un Button est d'ailleurs un évènement événement intégré de base à la classe Button. Pour cela, il faut utiliser le paramètre command de cette classe. Voyons maintenant comment en créer de nouveau.

Commençons par redonner le code de notre animation pacman (avec toujours le même print de test que j'avais oublié de supprimer une fois les tests validés...).

#!/usr/bin/env python

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

from tkinter import *

import random

vit_x = 10

vit_y = 0

vit_x2 = -10

vit_y2 = 0

animation_active = False

stop_animation = False


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

# Fonctions

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

def modification():

    global vit_x

    global vit_y

    global animation_active

    global stop_animation

    animation_active = True

    mod_angle = 0

    liste_coord = monCanvas.coords(pacman_1)

    liste_items = monCanvas.find_overlapping(liste_coord[0],liste_coord[1],liste_coord[2],liste_coord[3])

    test_collision = False

    if len(liste_items) > 1 :

        for x in liste_items :

            if x != pacman_1 :

                test_collision = True

                break

    if test_collision == True :

        monCanvas.itemconfig(pacman_1, fill="red")

    else:

        monCanvas.itemconfig(pacman_1, fill="yellow")

    if liste_coord[2]>500 :

        vit_x = -10

    elif liste_coord[0]<0 :

        vit_x = 10

    elif liste_coord[1]<0 :

        vit_y = 10

    elif liste_coord[3]>600 :

        vit_y = -10

    if (vit_x <0 or vit_y>0) :

        mod_angle = 180

    if vit_y == 0 :

        if (monCanvas.itemcget(pacman_1, 'start') == '15.0' or monCanvas.itemcget(pacman_1, 'start') == '195.0') :

            monCanvas.itemconfig(pacman_1, start=30+mod_angle, extent=300)

        else:

            monCanvas.itemconfig(pacman_1, start=15+mod_angle, extent=330)

    if vit_x ==0 :

        if (monCanvas.itemcget(pacman_1, 'start') == '105.0' or monCanvas.itemcget(pacman_1, 'start') == '285.0') :

            monCanvas.itemconfig(pacman_1, start=120+mod_angle, extent=300)

        else:

            monCanvas.itemconfig(pacman_1, start=105+mod_angle, extent=330)

    monCanvas.move(pacman_1,vit_x,vit_y)

    modification2()

    if stop_animation == False :

        fen_princ.after(100, modification)

    else:

        stop_animation = False

        animation_active = False


def init_couleurs():

    but_avance.config(bg="black")

    but_recule.config(bg="black")

    but_monte.config(bg="black")

    but_descend.config(bg="black")

    but_init.config(bg="red")

    but_arret.config(bg="red")


def avance_x():

    print(but_avance.cget('bg'))

    init_couleurs()

    but_avance.config(bg="blue")

    global vit_x

    global vit_y

    global stop_animation

    stop_animation = False

    vit_x = 10

    vit_y = 0

    if animation_active == False :

        modification()


def recule_x():

    init_couleurs()

    but_recule.config(bg="blue")

    global vit_x

    global vit_y

    global stop_animation

    stop_animation = False

    vit_x = -10

    vit_y = 0

    if animation_active == False :

        modification()


def monte_y():

    init_couleurs()

    but_monte.config(bg="blue")

    global vit_x

    global vit_y

    global stop_animation

    stop_animation = False

    vit_x = 0

    vit_y = -10

    if animation_active == False :

        modification()


def descend_y():

    init_couleurs()

    but_descend.config(bg="blue")

    global vit_x

    global vit_y

    global stop_animation

    stop_animation = False

    vit_x = 0

    vit_y = 10

    if animation_active == False :

        modification()


def point_depart():

    init_couleurs()

    but_init.config(bg="blue")

    global vit_x

    global vit_y

    vit_x = 0

    vit_y = 0

    arret()

    monCanvas.coords(pacman_1,50,50,150,150)

    monCanvas.itemconfig(pacman_1, start=15,extent=330)

    monCanvas.coords(pacman_2,350,350,450,450)

    monCanvas.itemconfig(pacman_2, start=15,extent=330)


def arret():

    init_couleurs()

    but_arret.config(bg="blue")

    global stop_animation

    stop_animation = True


def modification2():

    global vit_x2

    global vit_y2

    if animation_active == True :

        direction = random.randint(1,100)

        if direction > 80 :

            if direction > 95 :

                vit_x2 = 10

                vit_y2 = 0

            elif direction > 90 :

                vit_x2 = -10

                vit_y2 = 0

            elif direction > 85 :

                vit_x2 = 0

                vit_y2 = 10

            else:

                vit_x2 = 0

                vit_y2 = -10

        mod_angle = 0

        liste_coord = monCanvas.coords(pacman_2)

        if liste_coord[2]>500 :

            vit_x2 = -10

        elif liste_coord[0]<0 :

            vit_x2 =10

        elif liste_coord[1]<0 :

            vit_y2 = 10

        elif liste_coord[3]>600 :

            vit_y2 = -10

        if (vit_x2 <0 or vit_y2>0) :

            mod_angle = 180

        if vit_y2 == 0 :

            if (monCanvas.itemcget(pacman_2, 'start') == '15.0' or monCanvas.itemcget(pacman_2, 'start') == '195.0') :

                monCanvas.itemconfig(pacman_2, start=30+mod_angle, extent=300)

            else:

                monCanvas.itemconfig(pacman_2, start=15+mod_angle, extent=330)

        if vit_x2 ==0 :

            if (monCanvas.itemcget(pacman_2, 'start') == '105.0' or monCanvas.itemcget(pacman_2, 'start') == '285.0') :

                monCanvas.itemconfig(pacman_2, start=120+mod_angle, extent=300)

            else:

                monCanvas.itemconfig(pacman_2, start=105+mod_angle, extent=330)

        monCanvas.move(pacman_2,vit_x2,vit_y2)


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

# Corps du programme

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

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=600, bg='ivory', bd=0, highlightthickness=0)

monCanvas.grid(row=0,column=0, padx=10,pady=10)

pacman_1 = monCanvas.create_arc(50,50,150,150,fill="yellow",start=15,extent=330)

pacman_2 = monCanvas.create_arc(350,350,450,450,fill="orange",start=15,extent=330)


zone2 = Frame(fen_princ, bg='#777777')

zone2.grid(row=0,column=1,ipadx=5)


but_avance = Button(zone2, text="Droite", fg="yellow", bg="black", command=avance_x)

but_recule = Button(zone2, text="Gauche", fg="yellow", bg="black", command=recule_x)

but_monte = Button(zone2, text="Monte", fg="yellow", bg="black", command=monte_y)

but_descend = Button(zone2, text="Descend", fg="yellow", bg="black", command=descend_y)

but_arret = Button(zone2, text="STOP", fg="yellow", bg="red", command=arret)

but_init = Button(zone2, text="INIT", fg="yellow", bg="red", command=point_depart)

but_avance.pack(fill=X)

but_recule.pack(fill=X)

but_monte.pack(fill=X)

but_descend.pack(fill=X)

but_arret.pack(fill=X)

but_init.pack(fill=X)


fen_princ.mainloop()

1 - Liaison entre événement et widget

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

La traduction d'événement est event.

Lorsqu'on constate un événement / event, encore faut-il le gérer.

C'est le rôle du gestionnaire d'événement, qu'on traduit par event handler. C'est ce gestionnaire qui va donner l'ordre à la fonction désignée de s'activer si tel ou tel événement survient.

Pour créer cette liaison, il faut utiliser la méthode bind. Allons voir dans la documentation :

bind(sequence=None, func=None, add=None) [#]

Adds an event binding to this widget.Usually, the new binding replaces any existing binding for the same event sequence. By passing in “+” as the third argument, the new callback is added to the existing binding.

Maintenant que nous avons posé le vocabulaire, comment parvenir à faire avancer le pacman à gauche lorsq'on appuie sur la flèche "gauche" ?

Recherchons les infos dans le code et dans la documentation :

01° Placer cette ligne dans le corps du programme, sous la déclaration du Canvas. Par exemple, juste en dessous de la création de pacman_1. On pourrait le mettre plus loin mais comme c'est pour bouger le pacman, autant regrouper. Lancez et ...

Rien. Pas terrible comme ligne de code. Si on se renseigne un peu plus, on peut voir que la touche se nomme bien Left mais que l'événement "Appui sur Left" se code par Key-Left.

02° Ni une, ni deux : utilisons ceci :

fen_princ.bind('<Key-Left>', recule_x)

Alors ?

Normalement, toujours rien... Enfin, si. Nous avons progressé : nous déclenchons une erreur !

Exception in Tkinter callback

Traceback (most recent call last):

  File "C:\Users\ho\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1550, in __call__

    return self.func(*args)

TypeError: recule_x() takes 0 positional arguments but 1 was given

Que nous dit la console ? Que recule_x n'attend aucune argument et que pourtant il en a reçu un ... Mais pourtant, nous n'avons rien envoyé !

Si ?

Si.

Pourquoi ? C'est simple : nous déclenchons un événement et cet événement est porteur d'informations : par exemple, les coordonnées de la souris à ce moment.

Bref, la fonction qu'on donne en argument de bind ne doit pas être n'importe quelle fonction : ce doit être une fonction pouvant recevoir les données de l'événement. Et devinez comment s'appelle ce type de fonction : un gestionnaire d'événement.

Le paramètre de la fonction se nommera souvent event ou evt, bref événement. Vous pourriez noter dudule, ça fonctionnerait aussi.

03° Créer la fonction gest_recule(event) qui sera le gestionnaire chargé de gérer l'appui sur la flèche gauche. Changer ensuite la méthode bind pour la relier à ce gestionnaire.

def gest_recule(event) :

    recule_x()

Donc le programme principal, on aura :

fen_princ.bind('<Key-Left>', gest_recule)

Normalement, cette fois, ça fonctionne.

04° A vous de gérer les autres mouvements. Quelques indices :

Vous devriez constater en plus que le button de la dernière action activée est devenu bleu puisqu'on gère cela dans les fonctions avance_x ...

Voilà pour l'introduction : souvenez vous que les événements et les gestionnaires d'événements ne sont pas juste des mots de vocabulaire pour faire sympa. Derrière, se cachent réellement des notions à gérer dans le code :

Bon, maintenant que vous avez bien compris la différence entre la touche (Left) et l'événement sur la touche (Key-...) , je vais pouvoir vous montrer que j'ai menti un peu plus haut (ce n'est pas la première que cela arrive, mais cela permet de simplifier ou de mettre une notion en avant).

En réalité, si fen_princ.bind('<Left>', recule_x) ne fonctionnait pas, c'était bien à cause du fait que la fonction, pas à cause de Left.

05° Supprimer les key lors de la déclaration de vos méthodes bind. Relancer.

fen_princ.bind('<Left>', gest_recule)

fen_princ.bind('<Right>', gest_avance)

fen_princ.bind('<Up>', gest_monte)

fen_princ.bind('<Down>', gest_descend)

Vous constatez que cela fonctionne. En réalité, par défaut Left est compris comme Key-Left. Nous allons détailler les autres options qui ne sont pas par défaut dans les parties suivantes.

2 - Les touches LETTRES et CHIFFRES du clavier (hors clavier numérique)

Le premier problème lorsqu'on cherche à créer une interaction, c'est d'abord de trouver le bon code de détection de l'événement. Vous trouverez ici une liste de certains événements "touches clavier", gérés par Tkinter.

Commençons par rajouter une gestionnaire d'événement qui va nous servir à comprendre ce qui se passe lorsqu'on a activé une touche :

06° Rajouter la déclaration d'une méthode bind et du gestionnaire dans la partie fonctions. Vérifier que cela fonctionne avec deux trois touches.

fen_princ.bind('<Any-KeyPress>', gest_affichage)

Cette déclaration d'événement veut dire : Si on appuie (KeyPress) sur n'importe quelle touche (Any). Nous verrons ensuite les différentes possibilités disponibles justemnt.

def gest_affichage(event) :

    print("-- DEBUT INFO --")

    print('type : \t', event.type)

    print('keysym : \t', event.keysym)

    print('keycode : \t', event.keycode)

    print('keysym_num : \t', event.keysym_num)

    print("Coordonnées pointeur souris")

    print(event.x," ",event.y)

    print("FIN INFO")

07° Appuyer sur la touche "a", puis "Verrouillage majuscule", puis "A".

Touche a :

-- DEBUT INFO --

type : 2

keysym : a

keycode : 65

keysym_num : 97

Coordonnées pointeur souris

411 -33

FIN INFO

Verrouillage MAJ :

-- DEBUT INFO --

type : 2

keysym : Caps_Lock

keycode : 20

keysym_num : 65509

Coordonnées pointeur souris

411 -33

FIN INFO

Touche A :

-- DEBUT INFO --

type : 2

keysym : A

keycode : 65

keysym_num : 65

Coordonnées pointeur souris

411 -33

FIN INFO

Commençons par remarquer que keysym contient la chaîne de caractère contenant la description de l'événement : a, Caps_Lock et A.

Nous voyons ensuite que A et a ont le même keycode : 65

Par contre, le keysym_num diffère : 65 pour A mais 97 pour a.

Cela ne vous rappelle rien ? Un gros indice pour ceux qui bloquent :

08° Ouvrir la fiche dans une autre fenêtre et chercher les codes ASCII de A et a.

On voit donc que :

Si on résume :

Pour les touches "lettres" :

Nom de la touchekeysym (caractère obtenu)keycodekeysym_num
A + Caps Lock activéA6565
B + Caps Lock activéB6666
. . .. . .. . .. . .
Y + Caps Lock activéY8989
Z + Caps Lock activéZ9090
Nom de la touchekeysym(caractère obtenu)keycodekeysym_num
Aa6597
Bb6698
. . .. . .. . .. . .
Yy89121
Zz90122

09° Vérifier que cela fonctionne avec les touches 0 à 9 (hors pavé numérique) :

Nom de la touchekeysym (caractère obtenu)keycodekeysym_num
0 + Caps Lock activé04848
1 + Caps Lock activé14949
2 + Caps Lock activé25050
3 + Caps Lock activé35151
4 + Caps Lock activé45252
5 + Caps Lock activé55353
6 + Caps Lock activé65454
7 + Caps Lock activé75555
8 + Caps Lock activé85656
9 + Caps Lock activé95757
Nom de la touchekeysym (caractère obtenu)keycodekeysym_num
0agrave à48224
1ampersand &4938
2eacute é50233
3quotedbl "5134
4quoteright '5239
5parenleft (5353
6minus -5445
7egrave è55232
8underscore _5695
9ccedilla ç57231

Attention : on notera que keysym est bien une chaîne de caractères contenant par exemple "agrave". C'est important d'y faire attention pendant les tests.

Si vous voulez utiliser d'autres touches, il vaut mieux vérifier les codes obtenus via tkinter avec la fonction qu'on a donné ici : le codage des autres touches est parfois étrange.

Ainsi si vous voulez lancer l'appel de gest_avance avec la touche &, il faut noter fen_princ.bind('<Key-ampersand>', gest_avance) ou fen_princ.bind('<ampersand>', gest_avance).

3 - Les touches non-alphanumériques

Dans la plupart des applications graphiques, on gère surtout ces touches-ci. CTRL, TAB ...

En voici la liste dans le sens de lecture suivant sur un clavier Windows : haut à gauche vers bas à gauche vers bas à droite puis haut à droite.

Nom de la touchekeysym (nom interne)keycodekeysym_num
TabulationTab965289
Verrouillage majusculeCaps_Lock2065509
Touche shift de gauche(maj)Shift_L1665505
Touche contrôle de gaucheControl_L1765507
Touche Windows de gaucheWin_L9165371
Touche Alt de gaucheAlt_L1865513
Spacespace3232
Touche Alt GrAlt_R1865514
Touche Windows de droiteWin_R9265372
Touche ApplicationsApp9365373
Touche contrôle de droiteControl_R1765508
Touche shift de droite(maj)Shift_R1665506
Touche EntréeReturn1365293
RetourBackSpace865288

Faites attention à space qui possède bien une minuscule, là où les autres touches ont des majuscules. Il doit y avoir une raison. Si je le découvre un jour, je tenterais d'éditer cette phrase !

10° Que remarquez-vous sur les keycodes des deux types ALT ou Controle ou Shift ? Ont-elles le même keysym_num ?

Et voici la fin avec la ligne de haut qui contient habituellement ESCAPE, les F, Impr.Ecran ... puis les blocs de droite qui comprend les flèches, les touches Insérer, Supprimer ...

Nom de la touchekeysym (nom interne)keycodekeysym_num
Flèche GaucheLeft3765361
Flèche HautUp3865362
Flèche DroiteRight3965363
Flèche BasDown4065364
Page précédentePrior3365365
Page suivanteNext3465366
InsérerInsert4565379
SupprimerDelete4665535
Début de ligneHome3665360
Fin de ligneEnd3565367
Nom de la touchekeysym (nom interne)keycodekeysym_num
Echap.Escape2765307
F1F111265470
F2F211365471
F3F311465472
F4F411565473
F5F511665474
F6F611765475
F7F711865476
F8F811965477
F9F912065478
F10F1012165479
F11F1112265480
F12F1212365481
Arrêt défilementScroll_Lock14565300
PausePause1965299

4 - Clavier numérique

Dernière partie d'énumération des touches avec le clavier numérique.

Il possède deux topologies différentes en fonction de l'activation ou non du Verrouillage Numérique.

Nom de la touchekeysym (nom interne)keycodekeysym_num
Verrouillage NumériqueNum_Lock14465407

Voici la version avec verrouillage numérique activé :

Nom de la touchekeysym (nom interne)keycodekeysym_num
Divisionslash11147
Multiplicationasterisk10642
Soustractionminus10945
Additionplus10743
EntréeReturn1365293
Virgule/Pointperiod11046
009648
119749
229850
339951
4410052
5510153
6610254
7710355
8810456
9910557

Attention : les chiffres, le +, le - ... ont ici le même keysym et keysym_num mais un code de touche keycode différent puisqu'il s'agit physiquement d'une autre touche ayant la même fonction. Si vous avez vraiment besoin de distinguer le 5 du clavier alpha ou numérique, vous savez maintenant comment faire.

Voici la version avec verrouillage numérique désactivé : aucun changement avec /, *, -, +, Entrée. Par contre, il y a du changement pour les chiffres et le point.

Nom de la touchekeysym (nom interne)keycodekeysym_num
Divisionslash11147
Multiplicationasterisk10642
Soustractionminus10945
Additionplus10743
EntréeReturn1365293
Nom de la touchekeysym (nom interne)keycodekeysym_num
Point/VirguleDelete4665535
0Insert4565379
1End3565367
2Down4065364
3Next3465366
4Left3765361
5Clear1265291
6Right3965363
7Home3665360
8Up3865362
9Prior3365365

On voit donc que ces touches se comportent alors comme les touches situées à droite du clavier alpha.

5 - Type d'événements

Depuis le début, nous n'utilisons ici qu'un type unique d'événement : l'événement dont le type est 2. Il s'agit de l'événement "Appui sur une touche".

Il en existe bien d'autres.

Voici une liste des plus utiles dans les applications habituelles.

.
Type (event.type)Nom à utiliser dans bindDescription
2KeyPressL'utilisateur vient d'appuyer sur la touche. On peut également utiliser uniquement Key. Exemple : '<KeyPress-Left>' ou '<Key-Left>' ou encore '<Left>'
3KeyReleaseL'utilisateur vient de relacher une touche. Exemple : '<KeyRelease-Left>'.
4ButtonL'utilisateur vient d'appuyer sur l'un des boutons de la souris. Exemple : '<Button-1>' ou '<Button-2>' ou '<Button-3>'. Si event est le nom donné au paramètre "événement" du gestionnaire, on peut obtenir les coordonnées à l'aide de event.x ou event.y. Si on utilise monCanvas.bind('<Button-1>', gest_avance), on activera gest_avance si on appuie sur le Bouton 1 de la souris alors qu'on est au dessus du Canvas. Si vous voulez que cela marche sur toute la fenêtre, il faut utiliser fen_princ.bind('<Button-1>', gest_avance).
5ButtonReleaseL'utilisateur vient de relacher l'un des boutons de la souris. Exemple : '<ButtonRelease-1>'. A priviligier par rapport au type 4 : l'utilisateur peut sortir du widget pour désactiver son action en cas d'erreur, alors que l'exécution est automatique en cas de type 4 (Button).
6MotionL'utilisateur a bougé la souris : '<Motion>'. Si on utilise monCanvas.bind('<Motion>', gest_avance), on lance l'appel de gest_avance lorsque la souris bouge au dessus du widget monCanvas.
7EnterL'utilisateur vient de faire rentrer la souris dans le Widget : '<Enter>'. Même remarque qu'au dessus, la seule différence étant qu'on ne relance pas l'appel si on bouge ensuite dans le widget. L'activation ne se fait que lors de la rentrée. monCanvas.bind('<Enter>', gest_avance) va lancer gest_avance lorsqu'on fait rentrer le pointeur de la souris dans monCanvas.
8LeaveL'utilisateur vient de faire sortir la souris d'un Widget : '<Leave>'. Même remarque qu'au dessus, la seule différence étant qu'on ne relance pas l'appel si on bouge ensuite dans le widget. L'activation ne se fait que lors de la sortie. monCanvas.bind('<Leave>', gest_recule) va lancer gest_recule lorsqu'on fait sortir le pointeur de la souris de monCanvas.

Il en existe beaucoup d'autres. Allez voir la documentation externe si vous désirez faire quelque chose qui n'est pas réalisable avec ceux-ci.

On notera que si vous voulez utiliser la saisie qu'un utilisateur a réalisé dans un Widget Entry, il faut utiliser par exemple monEntree.bind('<Return>', gestionnaire).

La source documentaire se trouve ici : Vers infohost : http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/event-types.html

En français, on trouve également ceci : Vers tkinter.fdex.eu : http://tkinter.fdex.eu/doc/event.html#types-d-evenements

Répondez aux questions ci-dessous en utilisant le tableau (ou une autre source d'informations). Piochez ce dont vous avez besoin.

11° Créer une gestion d'événement liée à la fenêtre faisant le lien entre l'appui sur la touche A et le fait d'avancer en x.

12° Créer une gestion d'événement liée à la fenêtre faisant stopper l'animation lorsqu'on lache le A.

13° Créer une gestion d'événement liée au Canvas donnant dans la console les coordonnées x,y du pointeur de la souris lorsqu'on appuie sur le bouton de votre choix de la souris.

Les coordonnées sont toujours les coordonnées propres au Widget. Ainsi, si on clique sur le Canvas, on obtient les coordonnées du point de vue du Canvas.

Pour convertir des coordonnées fenêtre en coordonnées Canvas, il faut utiliser les méthodes canvasx et canvasy. A vous de vous renseigner, sur http://effbot.org/tkinterbook/canvas.htm.

14° Créer une gestion d'événement liée au Canvas donnant dans la console les coordonnées x,y du pointeur de la souris lorsqu'on appuie sur le bouton de votre choix de la souris.

15° Modifier la fonction du gestionnaire pour activer avancer si x > 250 et reculer si x < 250.

16° Arrêter l'animation dès que le curseur sort du Canvas.

6 - Modificateurs d'événement

Nous allons voir qu'on peut aller plus loin avec la déclaration de l'événement.

On peut rajouter des informations devant l'événement pour le rendre plus complexe.

Ainsi :

On peut utiliser Alt ou Control ou ou Shift si on veut dire que la touche Alt (ou Control ou Shift) doit être enfoncée lors de l'événement.

Ainsi monCanvas.bind('<Alt-Button-1>', gest_monte) veut dire qu'on active gest_monte si on appuie ET qu'on maintient ALT enfoncé dans le même temps.

On peut utiliser Any pour généraliser l'événement.

Ainsi fen_princ.bind('<Any-KeyPress>', gest_monte) veut dire qu'on active gest_monte si on appuie sur n'importe quelle touche.

On peut utiliser Lock pour imposer que le verouillage majuscule soit actif.

Ainsi fen_princ.bind('<Lock-KeyRelease-A>', gest_monte) veut dire qu'on active gest_monte si on relache A alors que le verrouillage majuscule est actif.

17° Et si nous pouvions changer la vitesse de l'animation avec CTRL+ et CTRL- ? Rajouter les événements et le bon gestionnaire pour réaliser cela.

Par contre, il faut savoir que event.x va toujours vous donner les coordonnées locales liées au widget sur lequel vous avez cliqué. Ainsi si vous cliquez en haut à gauche d'un carré contenu dans une fenêtre, les coordonnées x,y de l'événement seront (0,0) même si le widget n'est pas à cet endroit.

Si vous voulez les coordonnées du click dans le contexte de la fenêtre, il va falloir utiliser event.x_root et event.y_rooot.

Un exemple ci-dessous vous permettra d'un peu mieux voir la différence entre les deux.

On crée 1 Label contenant du texte et 3 Label contenant une image.

Lorsqu'on clique sur l'écran, on lance l'appel à la fonction activation qui va :

18° Tester le programme ci-dessous pour tenter de voir la différence entre les deux types de coordonnées. Cliquez par exemple au même endroit sur les trois carrés initiaux.

#!/usr/bin/env python

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

from tkinter import *


from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définitions des fonctions utilisées

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


def activation(event) :

    print ("Vous avez cliqué sur ", event.widget)

    print ("Les coordonnées locales sont : ")

    # On affiche les coordonnées de l'événement, contenues dans x et y

    print(event.x)

    print(event.y)

    print ("Les coordonnées globales sont : ")

    # On affiche les coordonnées de l'événement avec x_root et y_root

    print(event.x_root)

    print(event.y_root)

    try:

        widgetClique = event.widget

        nouvelle_temp = Img.new("RGB", (40,40), (255,255,0))

        nouvelleTk_temp = ImageTk.PhotoImage(nouvelle_temp)

        widgetClique.configure(image = nouvelleTk_temp)

        # la ligne suivante évite au ramasse-miette de détruire nouvelleTk_temp car

        # cette variable n'est sinon plus pointée par une variable du corps du programme.

        # Sans cette ligne, nouvelleTk_temp serait inexistante hors de la fonction

        widgetClique.monEmplacement = nouvelleTk_temp

    except:

        print("On clique directement sur la fenêtre")


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

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

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


fen_princ = Tk()


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, text="Belles images variables en cliquant", width=70)

monAffichage.pack()


# Création des images de base


presentation = Img.new("RGB", (40,40), (0,0,255))

presentationTk = ImageTk.PhotoImage(presentation)

presentation2 = Img.new("RGB", (40,40), (0,0,255))

presentationTk2 = ImageTk.PhotoImage(presentation2)

presentation3 = Img.new("RGB", (40,40), (0,0,255))

presentationTk3 = ImageTk.PhotoImage(presentation3)


# Création des labels de type image associés aux presentationTk


carre1 = Label(fen_princ, image=presentationTk)

carre1.pack()


carre2 = Label(fen_princ, image=presentationTk2)

carre2.pack()


carre3 = Label(fen_princ, image=presentationTk3)

carre3.pack()


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

# Bouclage de la fenêtre fen_princ

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

fen_princ.bind('<Button-1>', activation )

fen_princ.mainloop()

Vous pouvez tester le programme : en cliquant sur un élément, on le transforme en carré jaune. Même le texte. Il manque un filtre ou deux !

Allez également voir la console pour voir ce qu'elle affiche : vous allez voir la différence entre les coordonnées locales x et globales x_root.

...CORRECTION...

Si on clique en haut à gauche des carrés, on obtient toujours presque les mêmes valeurs x et y comprises entre 1 et 5 pixels : c'est normal, il donne les coordonnées par rapport au coin haut-gauche du widget sur lequel vous avez cliqué.

Pas moyen donc de déterminer le widget sur lequel on voit de cliquer avec ces coordonnées.


Avec x_root et y_root, on obtient les coordonnées par rapport au coin haut-gauche de votre écran. C'est mieux, mais si on bouge la fenêtre, ca change aussi les coordonnées de x_root et y_root du coup...

Par contre, le nom pourrait être utile pour distinguer le widget.

7 - Connaitre le widget activé : event.widget

Voyons comment créer un filtre pour que le Labeltext ne se transforme pas en image mais simplement que la couleur du texte change. Pour cela, nous allons comparer l'adresse contenue dans event.widget et l'adresse de l'objet contenu dans monAffichage.

19° Modifier la fonction avec le code ci-dessous. Vérifier qu'on modifie maintenant le Labeltext comme indiqué.

    try:

        widgetClique = event.widget

        if widgetClique == monAffichage :

            widgetClique.configure(fg = "red")

        else:

            nouvelle_temp = Img.new("RGB", (40,40), (255,255,0))

            nouvelleTk_temp = ImageTk.PhotoImage(nouvelle_temp)

            widgetClique.configure(image = nouvelleTk_temp)

            # la ligne suivante évite au ramasse-miette de détruire nouvelleTk_temp car

            # cette variable n'est sinon plus pointée par une variable du corps du programme.

            # Sans cette ligne, nouvelleTk_temp serait inexistante hors de la fonction

            widgetClique.monEmplacement = nouvelleTk_temp

    except:

        print("On clique directement sur la fenêtre")

20° Tache finale : on veut pouvoir augmenter la taille des carrés de 10 pixels en largeur lorsqu'on clique un carré en appuyant sur CTRL et réduire de 10 lorsqu'on clique en appuyant ALT.

Il y a encore beaucoup à dire mais on rentre dans ce cas de plus en plus dans des détails. La documentation est votre amie. Vous avez maintenant assez de recul pour la comprendre un peu mieux. Avec tous les boutons, la souris et les event.x et event.y pour récupérer les coordonnées de la souris lors de l'événement, vous avez de quoi faire des interfaces très complexes.

Si vous voulez savoir sur quel widget vous venez de cliquer, aller voir l'activité Savoir où on clique.