#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
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
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
----------------------
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 )
#!/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
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
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
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
* 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 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
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.
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
J'ai apporté les modifs que tu m'a conseillé et ça marche à la perfection. Merci beaucoup
Penses aussi à jeter un oeil aux docstrings pour commenter tes méthodes
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 ?
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 ?
* 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
Et puis tant qu'a faire, utilise int()!
Je vais faire ça, j'ai déjà amélioré le 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.
J'ai fait une modif dans mon code, c'est à ça que tu pensais ?
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
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
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,
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
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.
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 Qui sait, ca te permettra peut-être de l'adapter à de multiples devises
Une fois ces paramètres passés, tu les recopies en attributs de ta classe pour pouvoir les changer dynamiquement dans l'appli
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
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é
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