Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 28/05/2008, à 16:43

arnaud_d

[Python] Déclencher évèvement sur modif d'un widget Entry

Bonjour,

Désolé,  mon titre n'est pas très équivoque !

Je débute en Python et je n'arrive vraiment pas à trouver comment gérer ce qui suit :

J'utilise Tkinter dans un prog où j'ai des Entry et des Label (disons un de chaque pour faire simple).

Je voudrais qu'à chaque fois que le contenu de l'Entry est modifié, le Label soit aussi modifié (mis à jour). Comment intercepter l'évènement ? Je sais faire si l'utilisateur valide avec la touche entrée (avec Entry(command = ma_fonction_qui_met_a_jour), mais moi je voudrais que l'utilisateur n'ai pas à faire entrée, que ce soit automatique.

Je sais pas si je suis très clair hmm

Merci pour votre aide en tous cas !

Arnaud

Hors ligne

#2 Le 29/05/2008, à 21:35

Clop'

Re : [Python] Déclencher évèvement sur modif d'un widget Entry

Bonsoir Arnaud smile

Essaie en chopant un event KeyPress, ca devrait suffire pour mettre à jour suffisament souvent ton Label :

import Tkinter 


class Main(Tkinter.Tk):
    def __init__(self,):
        Tkinter.Tk.__init__(self)
        self.tst_lbl=Tkinter.Label(self,text='Test : ')
        self.label=Tkinter.Label(self)
        self.entry=Tkinter.Entry(self)
        
        self.entry.bind('<KeyPress>',self.callback)
        
        self.tst_lbl.pack()
        self.label.pack()
        self.entry.pack()
        self.entry.focus_set()
        
    def callback(self,event):
        self.label.__setitem__('text',self.entry.get())
    
        
if __name__=="__main__":
    
    root = Main() 
    root.title('Test')
    root.mainloop()

Bonne fin de soirée smile


----------------------
Linux on the Root , un site qu'il fait bon feuilleter!
----------------------
PyQtRadio, un lecteur de webradio simple et léger! Installation multi-distrib'

Hors ligne

#3 Le 29/05/2008, à 22:29

arnaud_d

Re : [Python] Déclencher évèvement sur modif d'un widget Entry

Merci Clop', merci mille fois c'est exactement ce petit programme que je voulais réaliser. Mais en fait, le but principal est de faire fonctionner mon programme de calculateur de monnaie qui suit : (Ne me demande surtout pas l'intérêt lol)

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
# Calculateur de monnaie v3a
# arnaud_d
# Améliorations à faire pour la version 3: 
    # si un champ est modifié ça enlève le total
# Améliorations à faire pour la version 4:
    # interface en GTK
# 29 Mai 2008

from Tkinter import *

# Définition des constantes
liste_devises = [500,200,100,20,10,5,2,1,0.50,0.25]
seuil_billets = 100
symbole_devise = 'DKK'


class Champ:        # Comme on affiche autant de ligne que de pièces et billets, on va faire une classe
    def __init__(self, fenetre, val, ligne):        # Définition du constructeur
        self.valeur=val                             # On sauvegarde la valeur d'une unité (pièce/billet) dans un attribut d'instance
        if self.valeur >= 100 :                     # On détermine si on à affaire à
            support = 'Billet'                      # un billet
        else :                                      # ou
            support = 'Piece'                       # une pièce
        self.texte = support + 's de %s %s :' % (self.valeur,symbole_devise)    # Assemblage de la chaine de texte pour le Label
        Label(fenetre, text = self.texte).grid(row=ligne, column=1)        # Affichage du label à gauche (colonne 1)
        self.quantite=Entry(fenetre,width=4)                               # Création d'un champ d'entrée (Entry)
        self.quantite.bind('<KeyPress>',self.quantite.event_generate('<Control-C>'))        
        self.quantite.grid(row=ligne, column=2)                            # Placement de l'Entry à droite (colonne 2)
        
    def get_sous_total(self):       # Méthode calculant de sous-total correspondant au champ courant
        try :                       # Structure try car eval(un truc vide) renvoie une erreur
            self.sous_total=eval(self.quantite.get()) * self.valeur
        except :
            self.sous_total=0
        return self.sous_total

class Application(Tk):          # La classe Application dérive de la classe Tk car elle n'est composée que d'une seule fenêtre
    def __init__(self):         # Définition du constructeur de la classe Application
        Tk.__init__(self)       # appel du constructeur de la classe parente
        self.title('Calculateur de monnaie danoise')    # Titre de la fenêtre
       
        ligne = 1               # Chaque champ (1 billet ou une pièce) prend une ligne. On aurait pu commencer à 0.
        self.liste_refs_champs = [] # Initialisation d'une LISTE vide. Cette liste contiendra les références (pointeurs) vers les
                                    # champs.
        for valeur in liste_devises:  # Pour chaque valeur fiduciale
            ref_champ=Champ(self,valeur,ligne)              # Creer un champ (et l'afficher à la ligne courante) en sauvegardant sa 
            self.liste_refs_champs.append(ref_champ)    # référence dans ref_champ. Ensuite ajouter cette référence à la Liste
            ligne += 1                                  # Le prochain champ sera sur la ligne suivante...

        Button(self,text='Calculer',command=self.calculer).grid(row=ligne, column = 2, sticky=E)    # Création d'un bouton de calcul
        self.result=Label(self)         # Création d'un Label pour afficher le total (résultat)
        self.result.grid(row=ligne,column=1)    # Placement de ce Label
        self.bind('<Control-C>',self.calculer)        
        self.mainloop()                         # Bouclage (Execution)

    def calculer(self,event=None):     # Définition de la méthode calculer (le total)
        total = 0           # Le total est initialisé à 0
        for ref_champ in self.liste_refs_champs:    # Pour chaque référence de champ contenue de la liste (= pour chaque champ),         
            total += ref_champ.get_sous_total()     # incrementer le total du sous-total du champ
        self.result.configure(text=str(total) + ' ' + symbole_devise)   # Enfin, changer le texte du Label et écrire le total suivi de la devise

app=Application()

Ce programme regroupe presque tout ce que je sais faire de mieux en Python !

Malgré ton aide je bloque encore, je crois que le problème est plus profond cette fois : je pense que l'organisation de mon code n'est pas bonne. Je voudrais qu'à chaque fois que l'utilisteur change une entry, le total soit recalculé. Bref le but est de supprimer le bouton calculer.

J'ai voulu passer par un évènement mais je suis pas sur que ce soit génial....

Tu veux bien jeter un œil et me dire ce que tu en penses ?

Merci beaucoup pour ton aide !

Arnaud

Hors ligne

#4 Le 29/05/2008, à 23:17

Clop'

Re : [Python] Déclencher évèvement sur modif d'un widget Entry

Re-Bonsoir smile

T'était vraiment pas loin du but! Je suppose ( je ne suis aps du tout un expert de Tkinter) que la hiérarchisation des signaux ne marche pas dans ton cas et que donc ton event généré Control-C n'arrive jamais à ta fenêtre principale...

Pour résoudre cela ( et par la même occasion , rendre plus lisible le code, en tout cas à mes yeux), je te propose plutôt d'utiliser bind sur tes entry dans l'init de ta classe Application. Tu as crée une liste de références de tes Entry, autant s'en servir smile

D'autre part, je me suis rendu compte qu'il fallait mieux utiliser un event KeyRelease pour mettre à jour le label après l'apparition d'une nouvelle lettre. cela peut avoir des inconvénients (que se passe-t'il si je laisse la touche enfoncée?) mais c'est en tout cas plus juste pour une utilisation normale.


En conclusion, pour que cela marche, retirer tes appels à binds :

self.quantite.bind('<KeyPress>',self.quantite.event_generate('<Control-C>'))

et

self.bind('<Control-C>',self.calculer)

) puis en réaliser un nouveau dans la boucle for :

     

for valeur in liste_devises:  
   ref_champ=Champ(self,valeur,ligne)          
   self.liste_refs_champs.append(ref_champ)    
   ref_champ.quantite.bind('<KeyRelease>',self.calculer)
   ligne += 1

Et ca devrait rouler smile

Quelques conseils gratuits et ô combien subjectifs :
* Les commentaires, c'est bien, mais faut pas que ca gene le code. Python a une syntaxe ultra lisible, il faut en profiter. Perso, j'essaie de placer mes commentaires avant les portions de code qu'ils commentent et non à côté . Penses aussi à jeter un oeil aux docstrings pour commenter tes méthodes smile

* Au niveau de l'organisation essaie d'organiser un peu plus. Par exemple :

class Application(Tk):    
  def __init__(self):   
      Tk.__init__(self)
      self.title('Calculateur de monnaie danoise')   
      ligne = 1
      self.liste_refs_champs = [] 
      for valeur in liste_devises:
        ref_champ=Champ(self,valeur,ligne)              
        self.liste_refs_champs.append(ref_champ)    
        ref_champ.quantite.bind('<KeyRelease>',self.calculer)
        ligne += 1                                  
      Button(self,text='Calculer',command=self.calculer).grid(row=ligne, column = 2, sticky=E) 
      self.result=Label(self)
      self.result.grid(row=ligne,column=1)
      self.mainloop()

J'aurais écrit ça :

class Application(Tk):    
  def __init__(self):   
      Tk.__init__(self)
   
      ligne = 1
      self.liste_refs_champs = []
 
      for valeur in liste_devises:
        ref_champ=Champ(self,valeur,ligne)              
        self.liste_refs_champs.append(ref_champ)    
        ref_champ.quantite.bind('<KeyRelease>',self.calculer)
        ligne += 1      

      self.title('Calculateur de monnaie danoise')
      Button(self,text='Calculer',command=self.calculer).grid(row=ligne, column = 2, sticky=E) 
      self.result=Label(self)
      self.result.grid(row=ligne,column=1)
                            
      self.mainloop()

Ca ne fait pas une grande différence, mais je trouve ca plus lisible ( et un code lisible est un code qu'on peut comprendre, maintenir et faire évoluer!)

* Essaie d'utiliser des noms de variables (encore) plus parlants. par exemple, j'aurais utilisé self.quantiteEntry au lieu de self.quantite. De plus, plutôt que d'utiliser des variables globales, pourquoi ne pas les passer en paramètres à ton appli pour pouvoir l'utiliser pour plusieurs monnaies?

* Pour la prochaine version, penses à rajouter un peu plus de gestion d'erreurs, sur les entry par exemple. Tu testes la chaîne vide mais pas si c'est un entier! Ce n'est pas grave ici car une exception est levée aussi sur un eval d'une string sans 'sens' python, mais ca serait plus propre smile Et puis tant qu'a faire, utilise int()!

* Un dernier point, pourquoi utiliser self.sous_total? Ta méthode te renvoie une valeur, ca me parait suffisant, pas besoin d'en faire un attribut.


En tout cas, même si je critique beaucoup, c'est pas si mal ( voire vachement bien) d'obtenir un truc qui tient la route pour un débutant! Chapeau big_smile

Dernière modification par Clop' (Le 29/05/2008, à 23:27)


----------------------
Linux on the Root , un site qu'il fait bon feuilleter!
----------------------
PyQtRadio, un lecteur de webradio simple et léger! Installation multi-distrib'

Hors ligne

#5 Le 30/05/2008, à 12:34

arnaud_d

Re : [Python] Déclencher évèvement sur modif d'un widget Entry

Bonjour Clop',

Je ne sais vraiment pas comment te remercier pour ton aide. Merci pour ton message si rapide mais aussi si complet. Je prend note de tous tes conseils et vais les appliquer.

Clop' a écrit :

En conclusion, pour que cela marche, retirer tes appels à binds :

self.quantite.bind('<KeyPress>',self.quantite.event_generate('<Control-C>'))

et

self.bind('<Control-C>',self.calculer)

) puis en réaliser un nouveau dans la boucle for :

     

for valeur in liste_devises:  
   ref_champ=Champ(self,valeur,ligne)          
   self.liste_refs_champs.append(ref_champ)    
   ref_champ.quantite.bind('<KeyRelease>',self.calculer)
   ligne += 1

Et ca devrait rouler smile

J'ai apporté les modifs que tu m'a conseillé et ça marche à la perfection. Merci beaucoup

Clop' a écrit :

Penses aussi à jeter un oeil aux docstrings pour commenter tes méthodes smile

Ok je vais faire ça. Il faut le faire pour les classes et les méthodes ? Dans certains exemple j'ai vu que les docstrings étaient encadrées de 3 " pourquoi pas une seule ?

Clop' a écrit :

De plus, plutôt que d'utiliser des variables globales, pourquoi ne pas les passer en paramètres à ton appli pour pouvoir l'utiliser pour plusieurs monnaies?

Comment ça ? De telle sorte d'écrire à la fin

app=Application([500,200,100,20,10,5,2,1,0.50,0.25],100,'DKK')

par exemple ?

Clop' a écrit :

* Pour la prochaine version, penses à rajouter un peu plus de gestion d'erreurs, sur les entry par exemple. Tu testes la chaîne vide mais pas si c'est un entier! Ce n'est pas grave ici car une exception est levée aussi sur un eval d'une string sans 'sens' python, mais ca serait plus propre smile Et puis tant qu'a faire, utilise int()!

Je vais faire ça, j'ai déjà amélioré le int()...

Clop' a écrit :

* Un dernier point, pourquoi utiliser self.sous_total? Ta méthode te renvoie une valeur, ca me parait suffisant, pas besoin d'en faire un attribut.

J'ai fait une modif dans mon code, c'est à ça que tu pensais ?

Clop' a écrit :

En tout cas, même si je critique beaucoup, c'est pas si mal ( voire vachement bien) d'obtenir un truc qui tient la route pour un débutant! Chapeau big_smile

Merci beaucoup, ton compliment m'encourage beaucoup ! Pour être franc, je suis débutant en Python mais pas en programmation alors je n'ai pas tant de mérite que ça wink

Encore merci pour ton aide, en voici le résultat :

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
# Calculateur de monnaie v3
# arnaud_d & Clop'
# Améliorations à faire pour la version 3.1: 
    # Améliorer la gestion d'erreur
    # Mettre plus de docstrings et moins de commentaires
    # Noms de variables encore plus parlants
# Améliorations à faire pour la version 4:
    # interface en GTK
# 29 Mai 2008

from Tkinter import *

# Définition des constantes
liste_devises = [500,200,100,20,10,5,2,1,0.50,0.25]
seuil_billets = 100
symbole_devise = 'DKK'


class Champ:        # Comme on affiche autant de ligne que de pièces et billets, on va faire une classe
    "Un Champ est composé d'un Label (la valeur fiducière) et d'une Entry (combien de pièces ou de billets)"
    
    def __init__(self, fenetre, val, ligne):        # Définition du constructeur
        self.valeur=val                             # On sauvegarde la valeur d'une unité (pièce/billet) dans un attribut d'instance

        if self.valeur >= 100 :                     # On détermine si on à affaire à
            support = 'Billet'                      # un billet
        else :                                      # ou
            support = 'Piece'                       # une pièce
        
        self.texte = support + 's de %s %s :' % (self.valeur,symbole_devise)    # Assemblage de la chaine de texte pour le Label
        Label(fenetre, text = self.texte).grid(row=ligne, column=1)        # Affichage du label à gauche (colonne 1)
        self.quantite=Entry(fenetre,width=4)                               # Création d'un champ d'entrée (Entry)
        self.quantite.grid(row=ligne, column=2)                            # Placement de l'Entry à droite (colonne 2)
        

    def get_sous_total(self): 
        "Cette méthode renvoie le sous-total associé à une valeur fiducière"
        
        try :                       # Structure try car eval(un truc vide) renvoie une erreur
            return int(self.quantite.get()) * self.valeur
        except :
            return 0



class Application(Tk):          # La classe Application dérive de la classe Tk car elle n'est composée que d'une seule fenêtre
    def __init__(self):         # Définition du constructeur de la classe Application
        Tk.__init__(self)       # appel du constructeur de la classe parente
        
        self.title('Calculateur de monnaie danoise')    # Titre de la fenêtre       
        ligne = 1               # Chaque champ (1 billet ou une pièce) prend une ligne. On aurait pu commencer à 0.
        self.liste_refs_champs = [] # Initialisation d'une LISTE vide. Cette liste contiendra les références (pointeurs) vers les
                                    # champs.
        for valeur in liste_devises:  # Pour chaque valeur fiduciale
            ref_champ=Champ(self,valeur,ligne)              # Creer un champ (et l'afficher à la ligne courante) en sauvegardant sa 
            self.liste_refs_champs.append(ref_champ)    # référence dans ref_champ. Ensuite ajouter cette référence à la Liste
            ref_champ.quantite.bind('<KeyRelease>',self.calculer)
            ligne += 1                                  # Le prochain champ sera sur la ligne suivante...

        self.result=Label(self)         # Création d'un Label pour afficher le total (résultat)
        self.result.grid(row=ligne,column=1)    # Placement de ce Label
        self.mainloop()                         # Bouclage (Execution)


    def calculer(self,event=None):
        "Méthode calculant le total"
        total = 0           # Le total est initialisé à 0
        
        for ref_champ in self.liste_refs_champs:    # Pour chaque référence de champ contenue de la liste (= pour chaque champ),         
            total += ref_champ.get_sous_total()     # incrementer le total du sous-total du champ

        self.result.configure(text=str(total) + ' ' + symbole_devise)   # Enfin, changer le texte du Label et écrire le total suivi de la devise


app=Application()

PS : Même si je ne me fais que peu d'illusions sur mon programme (Non, Microsoft ne va pas me piquer mon code et se faire une fortune), je voudrais savoir quelle habitude prendre pour dire "mon code est libre à tous mais laissez mon nom et rajoutez le votre si vous apportez des améliorations"

PS 2: Au fait si un économiste passe par là, je serais très intéressé de savoir s'il existe un mot pouvant désigner pièce et billet à la fois (je pense que "devise" n'est pas très juste), quand à "valeur fiduciaire" je suis sûr que son utilisation est impropre...

Dernière modification par arnaud_d (Le 30/05/2008, à 12:36)

Hors ligne

#6 Le 30/05/2008, à 16:43

Clop'

Re : [Python] Déclencher évèvement sur modif d'un widget Entry

Bonjour Arnaud,

arnaud_d a écrit :

Bonjour Clop',

Je ne sais vraiment pas comment te remercier pour ton aide. Merci pour ton message si rapide mais aussi si complet. Je prend note de tous tes conseils et vais les appliquer.
J'ai apporté les modifs que tu m'a conseillé et ça marche à la perfection. Merci beaucoup

C'est un plaisir smile

arnaud_d a écrit :

Ok je vais faire ça. Il faut le faire pour les classes et les méthodes ? Dans certains exemple j'ai vu que les docstrings étaient encadrées de 3 " pourquoi pas une seule ?

Tant que c'est un objet string, tu peux l'utiliser en docstring, mais les triples quotes (""") permettent d'écrire sur plusieurs lignes facilement, et ainsi d'obtenir une présentation plus claire de la docstring.

arnaud_d a écrit :

Comment ça ? De telle sorte d'écrire à la fin

app=Application([500,200,100,20,10,5,2,1,0.50,0.25],100,'DKK')

par exemple ?

Voila smile Qui sait, ca te permettra peut-être de l'adapter à de multiples devises smile Une fois ces paramètres passés, tu les recopies en attributs de ta classe pour pouvoir les changer dynamiquement dans l'appli smile

arnaud_d a écrit :

Je vais faire ça, j'ai déjà amélioré le int()...
J'ai fait une modif dans mon code, c'est à ça que tu pensais ?

POur les deux choses, oui smile

arnaud_d a écrit :

PS : Même si je ne me fais que peu d'illusions sur mon programme (Non, Microsoft ne va pas me piquer mon code et se faire une fortune), je voudrais savoir quelle habitude prendre pour dire "mon code est libre à tous mais laissez mon nom et rajoutez le votre si vous apportez des améliorations"

PS 2: Au fait si un économiste passe par là, je serais très intéressé de savoir s'il existe un mot pouvant désigner pièce et billet à la fois (je pense que "devise" n'est pas très juste), quand à "valeur fiduciaire" je suis sûr que son utilisation est impropre...

Je ne suis ni juriste ni économiste, donc bon.... Pour la question des licenses, j'utilise suivant mes projets soit la GPL soit la WTFL ( http://sam.zoy.org/wtfpl/), va faire un tour sur  http://opensource.org/ et fais ton marché smile

Bonne soirée!


----------------------
Linux on the Root , un site qu'il fait bon feuilleter!
----------------------
PyQtRadio, un lecteur de webradio simple et léger! Installation multi-distrib'

Hors ligne