IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Tkinter Python Discussion :

Problème de gestion des évènements


Sujet :

Tkinter Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Par défaut Problème de gestion des évènements
    --------- EDITION : --------------
    Attention, contrairement à ce que je croyais initialement, le problème n'a rien à voir avec les fonctions imbriquées.
    ------------------------------------


    Bonjour à tous,

    je suis confronté à un problème pour lequel je n'ai pas trouvé d'éléments de réponse dans la doc de tkinter et sur les forums.

    Il s'agit d'un problème tout bête de binding à l'intérieur de fonctions imbriquées. Le problème vient peut-être d'une erreur grossière de ma part, mais je n'arrive pas à voir laquelle.

    Voici un exemple du problème rencontré :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #!/usr/bin/env python3
     
    from tkinter import *
     
    class Dessin(Canvas) :
     
        "Permet de tracer des points rouges et de les relier par des traits noirs"
     
        def __init__(self, boss) :
            Canvas.__init__(self, boss, width = 650, height = 250, bg = "white")
            self.bind("<Button-1>", self.ajouter_sommet)
     
        def ajouter_sommet(self, event) :
            print("ajouter_sommet")
            (x, y) = (event.x, event.y)
            ref = self.create_oval(x - 10, y - 10, x + 10, y + 10, fill = "red")
            self.addtag_withtag("sommet", ref)
            self.tag_bind(ref, "<Button-1>", self.ajouter_arete)
     
        def ajouter_arete(self, event) :
            print("ajouter_arete")
            (x_ini, y_ini) = (event.x, event.y)
            self.ligne = self.create_line(x_ini, y_ini, x_ini, y_ini, width = 3)
     
            def actualiser(event) :
                print("actualiser")
                self.delete(self.ligne)
                self.ligne = self.create_line(x_ini, y_ini, 
                                                        event.x, event.y, width = 3)
     
            def annuler(event) :
                print("annuler")
                self.delete(self.ligne)
                self.unbind("<Motion>")
                self.bind("<Button-1>", self.ajouter_sommet)
                self.tag_bind("sommet", "<Button-1>", self.ajouter_arete)
     
            def ajouter(event) :
                print("ajouter")
                self.unbind("<Motion>")
                self.bind("<Button-1>", self.ajouter_sommet)
                self.tag_bind("sommet", "<Button-1>", self.ajouter_arete)
     
            self.bind("<Motion>", actualiser)
            self.bind("<Button-1>", annuler)
            self.tag_bind("sommet", "<Button-1>", ajouter)
     
     
    # --- Test de la classe ---
     
    if __name__ == '__main__' :
        test = Tk()
        Dessin(test).pack()
        test.mainloop()
    Normalement, ce code devrait faire la chose suivante : quand on clique-gauche dans le vide, un nouveau sommet est créé ; si on clique-gauche sur un sommet existant, une ligne noire est tracée entre le sommet et le curseur de la souris jusqu'à ce qu'on clique sur un autre sommet. Si on clique dans le vide, l'arête en cours de construction est effacée.

    J'ai choisi de définir des fonctions à l'intérieur de la fonction ajouter_arete car je trouve ça plus élégant, notamment car ça permet de faire passer un certain nombre d'arguments en variables libres des fonctions internes [ici, il y en a peu, mais dans mon vrai code il y en a un peu plus] et car ça permet d'avoir une unité cohérente (la fonction ajouter_arete) sans avoir tout un tas de micro fonctions qui se baladent autour

    Le problème rencontré est le suivant :

    lorsque la fonction ajouter_arete est appelé par bind en réponse à <Button-1>, la fonction annuler l'est également.


    Par exemple, lorsque je fais tourner le programme, j'obtiens :



    Ici, les deux sommets sont créés sans encombre, mais lorsque je clique sur l'un des deux, en apparence, "rien ne se passe" - On voit en fait que ceci est dû au fait que la fonction annuler a été appelée immédiatement après ajouter_arete.

    Peut-être que c'est dû au fait que lorsqu'une fonction est appelée via bind, le corps de la fonction se comporte comme si l'évènement l'ayant appelée était toujours actif ? (ça me semble bizarre mais ça expliquerait tout).

    Comment remédier à ce problème ? Quelqu'un possède-t-il de la documentation sur la façon dont les évènements sont gérés ? Par exemple s'ils étaient mis dans une pile, il suffirait d'utiliser une fonction de type "flush" pour résoudre le problème...

    Je débute en Python (mais pas tout à fait en programmation)

    Merci d'avance !

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 759
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 759
    Par défaut
    Salut,

    Citation Envoyé par drunkskater Voir le message
    Le problème rencontré est le suivant :

    lorsque la fonction ajouter_arete est appelé par bind en réponse à <Button-1>, la fonction annuler l'est également.
    En voilà un sujet bien compliqué
    techniquement, les handlers qui ont été "bound" à "<Button-1>" sont appelées dans l'ordre handler l'ident, handler sur le tag, handler sur l'instance,... (voir la doc) => cela constitue une chaine de handlers.

    Si lors de l'exécution d'un handler, vous ajoutez un handler qui sera "suivant" dans la chaine, il est "normal" qu'il soit appelé...
    Le manuel dit que pour casser la chaine, i.e. ordonner de ne pas appeler les handlers suivants, il faut terminer par return "break".
    Hélas, je ne comprends pas pourquoi çà ne fonctionne pas ici... mais une façon de tester la théorie est de remplacer:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.bind("<Button-1>", annuler)
    par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.after_idle(lambda: self.bind("<Button-1>", annuler))
    On force la mainloop à déclarer ce bind "plus tard", après qu'il ait traité la pile d’événements (dont celui qui est en cours).

    Ceci dit, quelque soit l'ingéniosité employée, il serait préférable de faire des binds sur des events: on_click, on_move, ... (plutôt que sur des actions directement) et de déclencher des actions en fonction de tables état/transition:
    • le code sera plus "lisible" et "simple" à tester,
    • moins de bind, unbind et d'allez retour entre tkinter et tk...

    c'est vous qui voyez
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre éprouvé
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Par défaut
    Merci beaucoup pour cette réponse précise !

    J'avais trouvé un "workaround" compliqué en utilisant des redirections. Par exemple :

    On défini une fonction de redirection comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    def rediriger(event) :
        self.bind("<Button-1>", annuler)
    puis dans le corps d'ajouter_arete, on lie le clic à rediriger plutôt qu'à annuler :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.bind("<Button-1>", rediriger)
    Mais ça devient vite compliqué (ici ça va mais dans mon vrai programme où il y a un peu plus d'évènements c'était galère) et, ne connaissant pas les règles utilisées pour déterminer l'ordre dans lequel les évènements sont traités et craignant qu'il varie selon l'implantation de Python, je m'étais résigné...

    il serait préférable de faire des binds sur des events: on_click, on_move, ... (plutôt que sur des actions directement) et de déclencher des actions en fonction de tables état/transition
    C'est-ce que je vais faire, c'est sûr que ça sera plus lisible !

    Encore merci !

  4. #4
    Membre éprouvé
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Par défaut
    Citation Envoyé par drunkskater Voir le message
    Merci beaucoup pour cette réponse précise !

    J'avais trouvé un "workaround" compliqué en utilisant des redirections. Par exemple :

    On définit une fonction de redirection comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    def rediriger(event) :
        self.bind("<Button-1>", annuler)
    puis dans le corps d'ajouter_arete, on lie le clic à rediriger plutôt qu'à annuler :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.bind("<Button-1>", rediriger)
    Mais ça devient vite compliqué (ici ça va mais dans mon vrai programme où il y a un peu plus d'évènements c'était galère) et, ne connaissant pas les règles utilisées pour déterminer l'ordre dans lequel les évènements sont traités et craignant qu'il varie selon l'implantation de Python, je m'étais résigné...



    C'est-ce que je vais faire, c'est sûr que ça sera plus lisible !

    Encore merci !

  5. #5
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 759
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 759
    Par défaut
    Super. Bon courage.

    @PauseKawa, si vous avez le temps et le courage de jouer avec ce code(*): pourquoi return "break" ne le fait pas ici?
    (*) J'avoue que j'ai craqué: impossible de suivre la logique sans ré-écrire le code... Ce n'est pas le but, et çà n'aide pas à comprendre!
    merci
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  6. #6
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    Pour expliquer la 'chose' il est sans doute bon de sortir du contexte:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from tkinter import *
     
    def onrectclick(e):
        print("click on rect")
     
    def onrootclick(e):
        print("click on root")
     
    root = Tk()
    can = Canvas(root, width=200, height=200)
    can.create_rectangle(0, 0, 200, 200, fill='red', tags=('rect'))
    can.tag_bind("rect", "<Button-1>", onrectclick)
    can.pack()
    root.bind("<Button-1>", onrootclick)
    root.mainloop()
    Output
    click on rect
    click on root
    Cela semble normal, non ?

    Maintenant voici se que dit le wiki tcl (C'est à propos de Text mais cela s'applique ici):
    If bindings are created for the widget as a whole using the bind command, then those bindings will supplement the tag bindings. The tag bindings will be invoked first, followed by bindings for the window as a whole.
    Ce qui nous amènes à la 'chose'
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from tkinter import *
     
    def onrectclick(e):
        print("click on rect")
        root.bind("<Button-1>", otherclick)
     
    def onrootclick(e):
        print("click on root")
     
    def otherclick(e):
        print("other click !")
     
    root = Tk()
    can = Canvas(root, width=200, height=200)
    can.create_rectangle(0, 0, 200, 200, fill='red', tags=('rect'))
    can.tag_bind("rect", "<Button-1>", onrectclick)
    can.pack()
    root.bind("<Button-1>", onrootclick)
    root.mainloop()
    Output
    click on rect
    other click !
    Le binding du tag est évalué en premier et change le bind de "<Button-1>" du widget. C'est ce qui arrive dans votre code ici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        def ajouter_arete(self, event):
            print("ajouter_arete")
            self.bind("<Button-1>", annuler)
            self.bind("<Motion>", actualiser)
    @+

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [AC-2003] Problème de gestion des événements
    Par Shankara dans le forum IHM
    Réponses: 4
    Dernier message: 27/03/2012, 11h56
  2. Problème gestion des évènements avec un JTree
    Par lyaminat dans le forum Composants
    Réponses: 2
    Dernier message: 14/10/2008, 15h44
  3. Réponses: 11
    Dernier message: 21/04/2008, 12h23
  4. Problème avec la gestion des événements
    Par Franck.H dans le forum SDL
    Réponses: 32
    Dernier message: 26/02/2007, 16h01
  5. Problème avec la gestion des événements
    Par CynO dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 17/10/2005, 10h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo