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

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

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    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 sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    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 régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    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 régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    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 sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    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
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    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)
    @+
    Merci d'utiliser le forum pour les questions techniques.

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Merci PauseKawa pour l'illustration des explications déjà données avec du vrai code.

    Mais, il y a quand même un joli "bug"... heu pour l'instant nous avons de la matière pour FAQ car ce comportement mérite peut-être clarification.

    Ré-écrivons un peu votre "code", la démo sera plus simple à construire qu'à partir de celui du PO.
    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
    import tkinter as tk
    state = ''
    def on_b1_click(name, event):
        w = event.widget
        log.debug('on_b1_click, name=%s, widget=%s' % (name, w))
        c_state = 'br_on_%s' % name
        if state == c_state:
            log.debug('break chain: %s' % c_state)
            return 'break'
     
    if __name__ == '__main__':
        import logging
        logging.basicConfig(level=logging.DEBUG)
        log = logging.getLogger('test')
     
        root = tk.Tk()
     
        tk.Button(root, text='next', command=root.quit).pack(side='top')
        can = tk.Canvas(root, width=200, height=200, name='canvas')
        can.create_rectangle(0, 0, 200, 200, fill='red', tags=('rect'))
     
        can.tag_bind('rect', "<Button-1>", lambda e: on_b1_click('rect', e))
        can.bind("<Button-1>", lambda e: on_b1_click('canvas', e))
        can.pack(side='bottom', fill='both')
        root.bind("<Button-1>", lambda e: on_b1_click('all', e))
     
        root.mainloop()
    Plutôt que d'avoir x handlers, je passe par un handler "nommé". Le but étant de faire varier son comportement en fonction de la valeur de "state".

    l'action "click sur le canvas" produit la sortie:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    DEBUG:test:on_b1_click, name=rect, widget=.canvas
    DEBUG:test:on_b1_click, name=canvas, widget=.canvas
    DEBUG:test:on_b1_click, name=all, widget=.canvas
    Nous avons chainé sur l'event <Button-1> un handler sur l'item, l'instance et "all", ils sont appelés successivement et nous sommes heureux d'avoir le comportement attendu.
    Cela exécuté, on clique sur le button "next" pour modifier un peu le comportement du handler:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        state = 'br_on_canvas'
        log.debug('------------------')
        log.debug('set state to "%s"' % state)
        root.mainloop()
    Le clic sur B1 va produire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    DEBUG:test:------------------
    DEBUG:test:set state to "br_on_canvas"
    DEBUG:test:on_b1_click, name=rect, widget=.canvas
    DEBUG:test:on_b1_click, name=canvas, widget=.canvas
    DEBUG:test:break chain: br_on_canvas
    le return "break" s'est bien traduit par ne pas appeler le handler suivant ('all').
    c'est toujours le comportement "attendu"... chouette.

    Maintenant, forcons le return "break" pour le handler déclaré sur l'item:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        state = 'br_on_rect'
        log.debug('------------------')
        log.debug('set state to "%s"' % state)
        root.mainloop()
    nous obtenons:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    DEBUG:test:------------------
    DEBUG:test:set state to "br_on_rect"
    DEBUG:test:on_b1_click, name=rect, widget=.canvas
    DEBUG:test:break chain: br_on_rect
    DEBUG:test:on_b1_click, name=canvas, widget=.canvas
    DEBUG:test:on_b1_click, name=all, widget=.canvas
    les handlers suivants sont quand même appelés.

    Si on reprend la doc:
    It is possible for multiple bindings to match a particular event. This could occur, for example, if one binding is associated with the item's id and another is associated with one of the item's tags. When this occurs, all of the matching bindings are invoked. A binding associated with the all tag is invoked first, followed by one binding for each of the item's tags (in order), followed by a binding associated with the item's id. If there are multiple matching bindings for a single tag, then only the most specific binding is invoked. A continue command in a binding script terminates that script, and a break command terminates that script and skips any remaining scripts for the event, just as for the bind command.
    nous pourrions espérer que les bindings sur les items s'ajoutent à la chaîne des handlers. Mais c'est un peu comme si nous avions une chaîne de handlers côté items et une autre côté widgets et le "break" ne se propage pas de l'une à l'autre...
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Bonjour wiztricks,

    and a break command terminates that script and skips any remaining scripts for the event, just as for the bind command.
    Citation Envoyé par wiztricks Voir le message
    nous pourrions espérer que les bindings sur les items s'ajoutent à la chaîne des handlers. Mais c'est un peu comme si nous avions une chaîne de handlers côté items et une autre côté widgets et le "break" ne se propage pas de l'une à l'autre...
    Il me semble aussi que les binds pour le Canvas (et Text) sont spécifiques au Widget. Le
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if {"[function %# %b %f %h %k %s %t %w %x %y %A %E %K %N %W %T %X %Y %D]" == "break"} break
    ne semble s'appliquer qu'au items (?) et ne pas se propager au bindtag. A moins que cela ne soit pas comme dans le bind et que cela ne soit que la fonction registered ? A tester tout cela.

    Il me semble avoir déjà lu une explication sur le net à ce propos mais impossible de remettre la main dessus.

    @+
    Merci d'utiliser le forum pour les questions techniques.

  9. #9
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Ceci dit le bind en tcl/tk c'est
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package require Tk
     
    proc onclick {object} {
      puts $object
    }
     
    canvas .c -width 400 -height 400
    set rect [.c create rectangle 0 0 400 400 -fill red]
     
    .c bind $rect <Button-1>  {onclick $rect}
     
    grid .c -row 0 -column 0
    Je me demande si cela ne viens pas de self._bind (tagOrId)
    Merci d'utiliser le forum pour les questions techniques.

  10. #10
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Citation Envoyé par PauseKawa Voir le message
    Je me demande si cela ne viens pas de self._bind (tagOrId)
    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
        def _bind(self, what, sequence, func, add, needcleanup=1):
            """Internal function."""
            if type(func) is StringType:
                self.tk.call(what + (sequence, func))
            elif func:
                funcid = self._register(func, self._substitute,
                            needcleanup)
                cmd = ('%sif {"[%s %s]" == "break"} break\n'
                       %
                       (add and '+' or '',
                    funcid, self._subst_format_str))
                self.tk.call(what + (sequence, cmd))
                return funcid
            elif sequence:
                return self.tk.call(what + (sequence,))
            else:
                return self.tk.splitlist(self.tk.call(what))
    Même pas...
    Merci d'utiliser le forum pour les questions techniques.

  11. #11
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Bonjour,

    En fait que cela soit
    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
    from tkinter import *
     
    def onrectclick(e):
        print("onrectclick")
        root.bind("<Button-1>", onotherclick)
        return "break"
     
    def onrootclick(e):
        print("onrootclick")
     
    def onotherclick(e):
        print("onotherclick !")
     
    root = Tk()
    c = Canvas(root, width=400, height=400)
    r = c.create_rectangle(0, 0, 400, 400, fill="red")
    root.bind("<Button-1>", onrootclick)
    c.tag_bind(r, "<Button-1>", onrectclick)
    c.pack()
    root.mainloop()
    Et
    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
    package require Tk
     
    proc onrectclick {} {
      puts "onrectclick"
      bind . <Button-1>  {onotherclick}
      return -code break
    }
     
    proc onrootclick {} {
      puts "onrootclick"
    }
     
    proc onotherclick {} {
      puts "onotherclick !"
    }
     
    canvas .c -width 400 -height 400
    set rect [.c create rectangle 0 0 400 400 -fill red]
     
    bind . <Button-1>  {onrootclick}
    .c bind $rect <Button-1>  {onrectclick}
     
    pack .c
    Le résultat est le même:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    onrectclick
    onotherclick !
    De même

    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("onrectclick")
        root.bind("<Button-1>", onotherclick)
        return "break"
     
    def onrootclick(e):
        print("onrootclick")
     
    def onotherclick(e):
        print("onotherclick !")
     
    root = Tk()
    frm = Frame(root, bg="red", width=400, height=400)
    frm.bind("<Button-1>", onrectclick)
    root.bind("<Button-1>", onrootclick)
    frm.pack()
    root.mainloop()
    Et
    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
    package require Tk
     
    proc onrectclick {} {
      puts "onrectclick"
      bind . <Button-1>  {onotherclick}
      return -code break
    }
     
    proc onrootclick {} {
      puts "onrootclick"
    }
     
    proc onotherclick {} {
      puts "onotherclick !"
    }
     
    frame .frm -width 400 -height 400 -background red
     
    bind . <Button-1>  {onrootclick}
    bind .frm <Button-1>  {onrectclick}
     
    pack .frm
    Donne le même résultat
    Cela viens de Tcl/Tk.

    @+
    Merci d'utiliser le forum pour les questions techniques.

  12. #12
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Salut,

    Merci pour votre temps.
    Je n'ai réussi qu'à trouver une entrée dans le wiki tcl/tk qui raconte ce comportement. Ce n'est pas forcément "anormal". Mais il faut du courage pour aller lire le code TCL/TK: il fait trop chaud
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  13. #13
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    C'est de là d’où viens ma citation plus haut.
    Je pose la question sur le bon forum pour voir.

    @+
    Merci d'utiliser le forum pour les questions techniques.

  14. #14
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Bonjour,

    Je reviens sur le sujet avec quelques tests, n'ayant pas de réponse "officielle" a donner.

    Dans un premier temps je pense qu'il y a confusion sur le mots 'tag'.
    Dans un cas il est utilisé pour définir un Widget (/toplevel), une classe ou all (voir bindtags) et dans l'autre une annotation donnée pour un élément d'un Canvas ou du texte d'un Widget Text*. Cette 'marque' pouvant servir a faire un binding.
    A ce niveau on pourrais différencier text binding/item binding et widget binding. Le code suivant montre qu'ils sont différenciés par l'interpréteur.
    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
    from tkinter import *
     
    def tcallback(e):
        print('t')
     
    def tagcallback(e):
        print('tag')
     
    root = Tk()
    t = Text(root)
    t.pack()
    t.insert(END, "ABC", ("Tag1", "Tag2"))
    t.bind("<1>", tcallback)
    t.tag_bind("Tag1", "<1>", tagcallback)
    t.tag_bind("Tag2", "<1>", tagcallback)
    root.mainloop()
    Output
    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
    from tkinter import *
     
    def tcallback(e):
        print('t')
     
    def tagcallback(e):
        print('tag')
        return 'break'
     
    root = Tk()
    t = Text(root)
    t.pack()
    t.insert(END, "ABC", ("Tag1", "Tag2"))
    t.bind("<1>", tcallback)
    t.tag_bind("Tag1", "<1>", tagcallback)
    t.tag_bind("Tag2", "<1>", tagcallback)
    root.mainloop()
    Output
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    text .t
    pack .t
    .t insert 1.0 "ABC" {Tag1 Tag2}
    bind .t <1> "puts .t"
    .t tag bind Tag1 <1> "puts tag; break"
    .t tag bind Tag2 <1> "puts tag; break"
    Output
    La doc est plus obscure
    Canvas
    This command associates command with all the items given by tagOrId such that whenever the event sequence given by sequence occurs for one of the items the command will be invoked
    ...
    This widget command is similar to the bind command except that it operates on items in a canvas rather than entire widgets. See the bind manual entry for complete details on the syntax of sequence and the substitutions performed on command before invoking it.
    Text
    It is possible for the current character to have multiple tags, and for each of them to have a binding for a particular event sequence. When this occurs, one binding is invoked for each tag, in order from lowest-priority to highest priority. If there are multiple matching bindings for a single tag, then the most specific binding is chosen (see the manual entry for the bind command for details). continue and break commands within binding scripts are processed in the same way as for bindings created with the bind command.
    ...
    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.
    C'est une chose a prendre en compte si l'on désire éviter un beau chassé croisé en manipulant la même chose dans des binds Items/Char/Widgets.

    Comment éviter cette 'propagation' ? Le plus simple me semble le bindtags
    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
    from tkinter import *
     
    def tcallback(e):
        print('t')
     
    def rcallback(e):
        print('Root')
     
    def tagcallback(e):
        print('Tag')
     
    def stopevent(e):
        print('Stop event !')
        return 'break'
     
    root = Tk()
    t = Text(root)
    t.pack()
    t.insert(END, "ABC", "Tag")
    root.bind("<1>", rcallback)
    t.bind("<1>", tcallback)
    t.tag_bind("Tag", "<1>", tagcallback)
    t.bind_class("dontpropagate", "<1>", stopevent)
    t.bindtags(tuple(("dontpropagate %s" % " ".join(t.bindtags())).split()))
    root.mainloop()
    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
    from tkinter import *
     
    def tcallback(e):
        print('t')
     
    def rcallback(e):
        print('Root')
     
    def tagcallback(e):
        print('Tag')
        oldtags = t.bindtags()
        t.bind_class("dontpropagate", "<1>", stopevent)
        t.bindtags(tuple(("dontpropagate %s" % " ".join(t.bindtags())).split()))
        print(t.bindtags())
        root.after(1, lambda: t.bindtags(oldtags) or t.unbind("dontpropagate"))
     
    def stopevent(e):
        print('Stop event !')
        return 'break'
     
    def PrintBT():
        print(t.bindtags())
     
     
    root = Tk()
    t = Text(root)
    t.pack()
    t.insert(END, "ABC", "Tag")
    root.bind("<1>", rcallback)
    t.bind("<1>", tcallback)
    t.tag_bind("Tag", "<1>", tagcallback)
    Button(root, text="Print bintags", command=PrintBT).pack()
    root.mainloop()
    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
    from tkinter import *
     
    def onrectclick(e):
        print("click on rect")
        oldtags = can.bindtags()
        can.bind_class("dontpropagate", "<1>", stopevent)
        can.bindtags(tuple(("dontpropagate %s" % " ".join(can.bindtags())).split()))
        root.after(1, lambda: can.bindtags(oldtags) or can.unbind("dontpropagate"))
        root.bind("<Button-1>", otherclick)
     
    def onrootclick(e):
        print("click on root")
     
    def otherclick(e):
        print("other click !")
     
    def stopevent(e):
        print('Stop event !')
        return 'break'
     
    root = Tk()
    can = Canvas(root, width=200, height=200)
    can.create_rectangle(50, 50, 150, 150, fill='red', tags=('rect'))
    can.tag_bind("rect", "<Button-1>", onrectclick)
    can.pack()
    root.bind("<Button-1>", onrootclick)
    root.mainloop()
    Tcl/Tk
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    text .t
    pack .t
    .t insert 1.0 "ABC" Tag
    bind Text <1> "puts Text"
    bind .t <1> "puts t"
    .t tag bind Tag <1> "puts Tag; break"
    # Supprimer le code ci dessou pour un comportement 'normal'
    bind stopevent <1> {if {[lsearch [%W tag names @%x,%y] Tag] >= 0} break}
    bindtags .t {stopevent .t Text . all}
    Votre avis ?

    @+

    * Le 'souci' est le même que celui montré plus haut
    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
    from tkinter import *
     
    def tcallback(e):
        print('t')
     
    def tagcallback(e):
        print('tag')
        return 'break'
     
    root = Tk()
    t = Text(root)
    t.pack()
    t.insert(END, "ABC", "Tag")
    t.bind("<1>", tcallback)
    t.tag_bind("Tag", "<1>", tagcallback)
    root.mainloop()
    Output
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    text .t
    pack .t
    .t insert 1.0 "ABC" TTag
    bind .t <1> "puts .t"
    .t tag bind TTag <1> "puts tag; break"
    Output
    Merci d'utiliser le forum pour les questions techniques.

  15. #15
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    A noter pour drunkskater que tout ceci est inutile dans le cadre du code présenté
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
        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)
            oldtags = self.bindtags()
            self.bind_class("dontpropagate", "<1>", lambda e: "break")
            self.bindtags(tuple(("dontpropagate %s" % " ".join(self.bindtags())).split()))
            self.after_idle(lambda: self.bindtags(oldtags) or self.unbind("dontpropagate"))
    Le simple .after_idle() de wiztricks est largement suffisant.
    Merci d'utiliser le forum pour les questions techniques.

  16. #16
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Plus simple

    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
    55
    56
    57
    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)
            self.eventset = False
     
        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)
            self.eventset = True
     
            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) :
                if self.eventset == True:
                    self.eventset = False
                    return "break"
                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()
    Mais
    Citation Envoyé par wiztricks Voir le message
    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
    A utiliser un 'flag' autant généraliser et faire un seul binding: self.state ayant des valeurs du style 'draw' et on gère suivant cette valeur dans le callback.
    Merci d'utiliser le forum pour les questions techniques.

  17. #17
    Expert confirmé 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
    Points : 4 005
    Points
    4 005
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    Mais, il y a quand même un joli "bug"... heu pour l'instant nous avons de la matière pour FAQ car ce comportement mérite peut-être clarification.
    Pas vraiment un 'bug' puisque l'on est averti à de multiples reprises.
    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.
    (C'est assez proche du 'programmeur responsable' Python et dans ce sens j'aime bien.)
    La seule difficulté étant lorsque l'on touche en parallèle au même comportement. C'est au programmeur d'en prendre compte.
    La discussion de savoir si l'on donne la possibilité de ne pas invoquer le Widget binding a sans doute eu lieu à l'époque entre quelques 'vieux barbus' mais l'avertissement a dus sembler assez explicite.
    Dans tous les cas impossible de mettre la main sur la 'discussion' en question.
    Il y a sans doute matière mais c'est assez difficile de se lancer juste sur des constatations.
    Merci d'utiliser le forum pour les questions techniques.

  18. #18
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Salut,
    Pardon de revenir tardivement sur ce sujet. D'abord, merci PauseKawa pour toutes ces informations.

    Je ne sais pas à quoi pouvaient penser les barbus lorsqu'ils ont fait çà.
    Toujours est-il qu'il y a plein de façon d'associer des events et de "trier" les différents cas. Une méthode possible est de récupérer l'item CURRENT:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        def get_current(self, tag=None):
            z = self.find_withtag(CURRENT)
            if len(z):
                item = z[0]
                if tag is None or tag in self.gettags(item):
                    return item
    S'il existe, on est sur qu'on a cliqué dessus.

    Ce qui me chagrine le plus dans le design original est la répétition des bind/unbind. Le principal ennui étant qu'unbind ne libère pas la mémoire sauf à préciser le nom de la commande qui a été retourné par .bind.
    Je m'explique avec un exemple bidon:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    >>> import tkinter as tk
    >>> c = tk.Label()
    >>> c.bind('<1>', lambda: None)
    '38683392<lambda>'
    le str "38683392<lambda>" sera le nom de la commande qui sera crée dans l'espace de noms de l'interprêteur TCL et associé à une fonction/callable.
    Comme c'est un espace "plat", ce nom doit être unique.
    38683392 est l'id de l'instance de tk.CallWrapper qui encapsule qui encapsule le callback/handler (lambda: None).
    Le nom de la commande est stocké dans la liste ._tclCommands
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    >>> c._tclCommands
    ['38683392<lambda>']
    associée au widget. Cela permet de les détruire via .destroy.

    Si on fait .unbind "simple":
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    >>> c.unbind('<1>')
    >>> c._tclCommands
    ['38683392<lambda>']
    La commande (et les structures qui font le pont entre TCL et Python) n'est pas détruite.
    Pour que le ménage soit fait, il faut passer le nom de la commande à .unbind.
    Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    >>> c = tk.Label()
    >>> cmd = c.bind('<1>', lambda: None)
    >>> c._tclCommands
    ['38683392<lambda>']
    >>> c.unbind('<1>', cmd)
    >>> c._tclCommands
    []
    C'est ce qui rend bind/unbind délicat dans le contexte du PO, car il faudrait explicitement détruire ou sauver/restorer pour faire "propre".

    Un truc que j'utilise dans ce cas, c'est "sauver/restorer" i.e. on ne crée plus de commandes on ré-utilise l'existant mais à l'étage "en dessous" de .bind.

    On recommence mais cette fois ci on ignore la commande pour s'intéresser au script TCL retourné par .bind('<1>'):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    >>> c = tk.Label()
    >>> c.bind('<1>', lambda: None)
    '38683392<lambda>'
    >>> tcl_script = c.bind('<1>')
    >>> tcl_script 
    'if {"[38683392<lambda> %# %b %f %h %k %s %t %w %x %y %A %E %K %N %W %T %X %Y %D
    ]" == "break"} break\n'
    L'appel d'.unbind sans le nom de la commande aura le même défaut que précédemment mais comme nous avons garder tcl_script nous allons pouvoir restorer .bind via:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    >>> c.unbind('<1>')   
    >>> c.bind('<1>')     # plus de script associé
    ''
    >>> c.bind('<1>', tcl_script)  # restoration
    >>> c.bind('<1>')
    'if {"[38683392<lambda> %# %b %f %h %k %s %t %w %x %y %A %E %K %N %W %T %X %Y %D
    ]" == "break"} break\n'


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

+ 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