À propos de : thread python tkinter
Bonjour,
je voudrais réaliser un objet graphique animé représentant une pompe pouvant être mise en rotation et à l'arrêt.
La création de l'objet pompe est concluante (voir image jointe). Mais quand la fonction run est présente (pas en commentaire), rien ne se passe et la fenêtre de l'image jointe ne s'affiche même pas.
Évidemment le fichier python joint est un peu long et peut-être pas très documenté ni peut-être très bien construit. Aussi je remercie par avance ceux qui prendraient le temps de se pencher dessus.
Cordialement.
je voudrais réaliser un objet graphique animé représentant une pompe pouvant être mise en rotation et à l'arrêt.
La création de l'objet pompe est concluante (voir image jointe). Mais quand la fonction run est présente (pas en commentaire), rien ne se passe et la fenêtre de l'image jointe ne s'affiche même pas.
Évidemment le fichier python joint est un peu long et peut-être pas très documenté ni peut-être très bien construit. Aussi je remercie par avance ceux qui prendraient le temps de se pencher dessus.
Cordialement.
""" Pompes ----------------------------------------------------------------- -- -- lA FONCTION RUN NE DONNE RIEN -- -- ----------------------------------------------------------------------------""" from threading import Thread import time from tkinter import * #-- application graphique (canevas , boutons , label) #============================================================================== class Point(): #-- les points s'affichent(si etat=1) dans le canevas #-- principal cnv à créer def __init__(self, x, y , etat=0): self.x = x self.y = y self.etat=etat if self.etat == 1 : cnv.create_oval(self.x-3 , self.y-3 ,self.x+3,self.y+3 , fill="black") def getX(self): return self.x def getY(self): return self.y def setX(self,val): self.x=val def setY(self,val): self.y=val def affich(self): cnv.create_oval(self.x-3 , self.y-3 ,self.x+3,self.y+3 , fill="black") #============================================================================== class Ligne (): #-- les lignes s'affichent dans le canevas #-- principal cnv à créer def __init__(self,pto, ptd): self.xo=pto.x self.yo=pto.y self.xd=ptd.x self.yd=ptd.y self.lgn=cnv.create_line (self.xo , self.yo , self.xd , self.yd , fill="blue" , width=3) def on (self): cnv.itemconfig(self.lgn, fill="blue") def off (self): cnv.itemconfig(self.lgn, fill="white") #============================================================================== class T_pompe(Thread): #-- def __init__(self, p_x , p_y): Thread.__init__(self) self.pos_x = p_x self.pos_y = p_y self.en_cours = True self._pause = False self.tab_lignes = [] #-- lignes représentant la pompe ec = 10 * 2**0.5 deb = Point(p_x , p_y) #-- début de toutes les lignes t_fin = [Point(p_x , p_y - 20) , Point(p_x-ec , p_y-ec) , Point(p_x-20 , p_y) , Point(p_x-ec , p_y+ec) , Point(p_x , p_y+20) , Point(p_x+ec , p_y+ec) , Point(p_x+20 , p_y) ,Point(p_x+ec , p_y-ec)] for i in range(8): #-- affichage de la pompe self.tab_lignes.append(Ligne(deb,t_fin[ i])) cnv.create_oval (p_x-22 , p_y-22 , p_x+22 , p_y+22 ) #--------------------------------------------------------------------- def run(self): #-- Code à exécuter pendant l'exécution du thread self.i = 0 self.j = 7 while self.en_cours == True : if self._pause == True : time.sleep(1.0) continue time.sleep(2.0) self.i = self.i+1 if self.i == 8 : self.i=0 cnv.itemconfig(self.tab_lignes[self.i], fill="white") time.sleep(0.1) cnv.itemconfig(self.tab_lignes[self.j], fill="blue") self.j=self.i-1 if self.j<0 : self.j=7 #--------------------------------------------------------------------- def reprise(self): self._pause = False #--------------------------------------------------------------------- def pause(self): self._pause = True #--------------------------------------------------------------------- def stop(self): self.en_cours = False #===================================================================== def reset(): #-- application print("recommencer") message.set("Résultats ou info") #------------------------------------------------------------------------------ #========= Fenêtre principale ======================================================= fen = Tk() #-- Mise en place de la fenetre et du canevas principal fen.title("MODELE") #========= VARIABLES ==================================================== message = StringVar() #-- utilisée dans info_label (affichage divers) #== zone pour afficher des textes (résultats ou autre) par message.set("....") info_label = Label(fen, textvariable=message , font = ("Helvetica 16 bold italic")) info_label.pack(side="top") message.set("Démarrage") #====== canevas éventuel ================================================= cnv = Canvas(fen, width=300, height= 200, bg="aliceblue") cnv.pack() #========== Boutons de base ============================================ #-- bouton possible ------- btn_new = Button(fen , text="Reset" ,font=("Helvetica", 18), command = reset) btn_new.pack(side="left") #-- bouton pour quiter ------- btn_quit = Button(fen , text=" Quitter " ,font=("Helvetica", 18), command= fen.destroy) btn_quit.pack(side="right") #============================================================================== p_1 = T_pompe(50,50) p_2 = T_pompe(100,50) #p_1.run() fen.mainloop()
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je regarde ça.
Habituellement, on crée une classe pour la fenêtre.
-- Schnoebelen, Philippe
3) Il va falloir revoir l'architecture. La plupart, sinon la totalité des toolkits graphiques ne supportent pas (sans risque élevé de crash) qu'on appelle leurs fonctions depuis un thread autre que le principal. Il faut donc faire en sorte que les “worker threads” passent des messages au thread principal quand celui-ci doit interagir avec Tkinter, et c'est lui qui doit appeler les fonctions ou méthodes de Tkinter. Cela doit pouvoir se faire par exemple avec les Condition Objects du module 'threading'. Il serait peut-être sage de réaliser l'ébauche du programme d'abord purement en mode texte, sans Tkinter, afin de bien se familiariser avec les threads. Une fois que ça marche bien, tu peux ajouter la couche graphique.
4) Les worker threads doivent être join()és par le thread principal, ou alors il faut les créer avec l'argument 'daemon=True' — s'ils supportent d'être arrêtés brutalement quand le thread principal se termine.
5) fen.quit() est sans doute un peu plus propre que fen.destroy() pour dire au-revoir à la fenêtre racine Tk.
Pour passer les données du “worker thread” au thread $I$, on peut utiliser un objet du type queue.Queue. Si je reprends mon code de l'époque et schématise la partie qui peut t'intéresser, cela donne ce qui suit (c'est un schéma, hein ; il faut compléter à certains endroits) :
Cordialement .
C'est a priori plus simple de cette façon car tout se passe dans le thread de l'interface. En revanche, si le code que tu fais appeler par la “idle function” prend une demi seconde pour s'exécuter, il va “freezer” l'interface graphique pendant une demi seconde, ce qui est très laid (ceci car la “idle function” est exécutée par la boucle d'évènements de Tk ; elle interrompt donc le traitement de ces derniers). Cette technique est acceptable si les durées d'exécution sont courtes à chaque fois.
Tu peux sans doute utiliser cette technique pour mettre à profit le temps processeur disponible quand la boucle d'évènements de Tk n'a plus rien à faire et le répartir entre tes différentes « unités de travail » (qui ne sont pas des threads, dans cette approche).
Edit : formulation plus précise.
Je vais pouvoir continuer mon appli .
Cordialement
after_idle(), c'est un peu le même principe sauf que la fonction que tu passes en argument sera exécutée dès que Tk aura traité tous les évènements en attente (autrement dit, très très bientôt, sauf si l'interface est dans un traitement particulièrement lourd, auquel cas elle apparaît comme « gelée » à l'utilisateur).
[En typographie, on ne met jamais d'espace avant un point ou une virgule, mais toujours après. ;-) AD]
[code]
""" Pompes
--
-- Exemple d'animation d'objets sous tkinter
--
--
"""
from tkinter import * #-- application graphique (canevas , boutons , label)
#==============================================================================
class Point(): #-- les points s'affichent(si etat=1) dans le canevas
#-- principal cnv à créer
def __init__(self, x, y , etat=0):
self.x = x
self.y = y
self.etat = etat
if self.etat == 1 : cnv.create_oval(self.x-3 , self.y-3 ,
self.x+3 , self.y+3 , fill="black")
def getX(self): return self.x
def getY(self): return self.y
def setX(self,val): self.x=val
def setY(self,val): self.y=val
def affich(self):
cnv.create_oval(self.x-3 , self.y-3 ,self.x+3,self.y+3 , fill="black")
#==============================================================================
class Ligne (): #-- les lignes s'affichent dans le canevas
#-- principal cnv à créer
def __init__(self,pto, ptd):
self.xo=pto.x
self.yo=pto.y
self.xd=ptd.x
self.yd=ptd.y
self.lgn=cnv.create_line (self.xo , self.yo ,
self.xd , self.yd , fill="blue" , width=3)
def on (self): cnv.itemconfig(self.lgn, fill="blue")
def off (self): cnv.itemconfig(self.lgn, fill="cyan")
#==============================================================================
class T_pompe(): #-- chaque pompe est représentée par 8 rayons dont on change
#-- un par un la couleur pour simuler la rotation
def __init__(self, p_x , p_y):
self.pos_x = p_x
self.pos_y = p_y
self.en_marche = False
self.tab_lignes = [] #-- rayons représentant la pompe
ec = 10 * 2**0.5
self.ray = 0 #-- n° du rayon de la pompe
self.j = 7 #-- rayon précédent
deb = Point(p_x , p_y) #-- début de toutes les lignes
t_fin = [Point(p_x , p_y - 20) , Point(p_x-ec , p_y-ec) , Point(p_x-20 , p_y) ,
Point(p_x-ec , p_y+ec) , Point(p_x , p_y+20) , Point(p_x+ec , p_y+ec) ,
Point(p_x+20 , p_y) ,Point(p_x+ec , p_y-ec)]
for i in range(8): #-- affichage de la pompe
self.tab_lignes.append(Ligne(deb,t_fin[ i]))
self.rond = cnv.create_oval (p_x-22 , p_y-22 , p_x+22 , p_y+22 ,
width=4 , outline="blue")
#
def marche(self): #-- animation des pompes par rotation des rayons
if self.en_marche == True :
self.ray = self.ray + 1
if self.ray == 8 : self.ray = 0
self.tab_lignes[self.ray].off()
cnv.itemconfig(self.rond, outline="red")
self.tab_lignes[self.j].on()
self.j = self.ray - 1
if self.j < 0 : self.j = 7
fen.after(50,self.marche)#-- la fonction marche est relancée toutes les 50ms
else:
self.tab_lignes[self.ray].on()
cnv.itemconfig(self.rond, outline="blue")
#
def start(self):
self.en_marche = True
self.marche()
#
def stop(self):
self.en_marche = False
self.marche()
#
def etat(self): return self.en_marche
#=====================================================================================
#========= Fenêtre principale =======================================================
fen = Tk() #-- Mise en place de la fenetre et du canevas principal
fen.title("POMPES")
#====== canevas =================================================
cnv = Canvas(fen, width=300, height= 200, bg="aliceblue")
cnv.pack()
#========= VARIABLES ====================================================
p1 = T_pompe(30,50)
p2 = T_pompe(80,50)
p3 = T_pompe(130,50)
#========= action des boutons ====================================================
def p1_cmd(): #-- commande p1
etat = p1.etat()
if etat == False : p1.start()
else : p1.stop()
#
def p2_cmd(): #-- commande p2
etat = p2.etat()
if etat == False : p2.start()
else : p2.stop()
#
def p3_cmd(): #-- commande p3
etat = p3.etat()
if etat == False : p3.start()
else : p3.stop()
#========== Boutons de base ============================================
#-- bouton pour quiter
btn_quit = Button(fen , text=" Quitter " ,font=("Helvetica", 18), command= fen.destroy)
btn_quit.pack(side="right")
#========== Boutons pompes ============================================
#-- bouton p1
btn_p1 = Button(fen , text="P1" ,font=("Helvetica", 16), command = p1_cmd)
btn_p1.pack(side="left")
#-- bouton p2
btn_p2 = Button(fen , text="P2 " ,font=("Helvetica", 16), command= p2_cmd)
btn_p2.pack(side="left")
#-- bouton p2
btn_p3 = Button(fen , text="P3 " ,font=("Helvetica", 16), command= p3_cmd)
btn_p3.pack(side="left")
fen.mainloop()
[code]