Comment utilisez-vous votre propre code à côté de la boucle d'événement de Tkinter?
Question
Mon petit frère commence à peine à programmer et, pour son projet Expo-sciences, il simule une volée d'oiseaux dans le ciel. La plupart de ses codes ont été écrits, et cela fonctionne bien, mais les oiseaux doivent se déplacer à chaque instant .
Tkinter, cependant, raccroche le temps de sa propre boucle d’événements et son code ne fonctionnera donc pas. Faire root.mainloop()
exécute, exécute et continue à fonctionner, et les seuls gestionnaires exécutés sont les gestionnaires d'événements.
Existe-t-il un moyen de faire en sorte que son code soit exécuté parallèlement à la boucle principale (sans lecture multiple, c'est compliqué et cela devrait rester simple), et si oui, de quoi s'agit-il?
À l’heure actuelle, il a trouvé un vilain piratage, liant sa move()
fonction à <b1-motion>
, de sorte que tant qu’il maintient le bouton enfoncé et fait bouger la souris, cela fonctionne. Mais il doit y avoir un meilleur moyen.
La solution
Utilisez la méthode after
sur l'objet Tk
:
from tkinter import *
root = Tk()
def task():
print("hello")
root.after(2000, task) # reschedule event in 2 seconds
root.after(2000, task)
root.mainloop()
Voici la déclaration et la documentation de la <=> méthode:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
Autres conseils
La solution publiée par Bjorn génère un & "; RuntimeError: appel de Tcl depuis un appartement différent " message sur mon ordinateur (RedHat Enterprise 5, python 2.6.1). Bjorn n’a peut-être pas reçu ce message puisque, selon un lieu I vérifié , la gestion incorrecte des threads avec Tkinter est imprévisible et dépend de la plate-forme.
Le problème semble être que app.start()
compte comme une référence à Tk, car app contient des éléments Tk. J'ai résolu ce problème en remplaçant self.start()
par un __init__
dans mainloop()
. J'ai également fait en sorte que toutes les références Tk se trouvent soit dans la fonction qui appelle <=> , soit dans des fonctions appelées par la fonction qui appelle <=> (this est apparemment essentiel pour éviter l'erreur & "; appartement différent &";).
Enfin, j'ai ajouté un gestionnaire de protocole avec un rappel, car sans cela, le programme se termine avec une erreur lorsque la fenêtre Tk est fermée par l'utilisateur.
Le code révisé est le suivant:
# Run tkinter code in another thread
import tkinter as tk
import threading
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Hello World")
label.pack()
self.root.mainloop()
app = App()
print('Now we can continue running code while mainloop runs!')
for i in range(100000):
print(i)
Lors de l'écriture de votre propre boucle, comme dans la simulation (je suppose), vous devez appeler la fonction update
qui fait ce que le mainloop
fait: met à jour la fenêtre avec vos modifications, mais vous le faites dans votre boucle .
def task():
# do something
root.update()
while 1:
task()
Une autre option consiste à laisser tkinter s'exécuter sur un thread séparé. Une façon de le faire est la suivante:
import Tkinter
import threading
class MyTkApp(threading.Thread):
def __init__(self):
self.root=Tkinter.Tk()
self.s = Tkinter.StringVar()
self.s.set('Foo')
l = Tkinter.Label(self.root,textvariable=self.s)
l.pack()
threading.Thread.__init__(self)
def run(self):
self.root.mainloop()
app = MyTkApp()
app.start()
# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')
Attention, la programmation multithread est difficile et il est très facile de se tirer une balle dans le pied. Par exemple, vous devez faire attention lorsque vous modifiez les variables de membre de la classe exemple ci-dessus afin de ne pas interrompre avec la boucle d'événement de Tkinter.
Ceci est la première version de travail de ce qui sera un lecteur GPS et un présentateur de données. tkinter est une chose très fragile avec trop peu de messages d’erreur. Il ne met pas de choses et ne dit pas pourquoi une grande partie du temps. Très difficile venant d'un bon développeur de formulaire WYSIWYG. Quoi qu’il en soit, cela exécute une petite routine 10 fois par seconde et présente les informations sur un formulaire. Il a fallu du temps pour y arriver. Quand j'ai essayé une valeur de minuterie de 0, la forme n'a jamais été soulevée. Ma tête me fait mal maintenant! 10 fois ou plus par seconde me suffisent. J'espère que ça aide quelqu'un d'autre. Mike Morrow
import tkinter as tk
import time
def GetDateTime():
# Get current date and time in ISO8601
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return (time.strftime("%Y%m%d", time.gmtime()),
time.strftime("%H%M%S", time.gmtime()),
time.strftime("%Y%m%d", time.localtime()),
time.strftime("%H%M%S", time.localtime()))
class Application(tk.Frame):
def __init__(self, master):
fontsize = 12
textwidth = 9
tk.Frame.__init__(self, master)
self.pack()
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Time').grid(row=0, column=0)
self.LocalDate = tk.StringVar()
self.LocalDate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalDate).grid(row=0, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Date').grid(row=1, column=0)
self.LocalTime = tk.StringVar()
self.LocalTime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalTime).grid(row=1, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Time').grid(row=2, column=0)
self.nowGdate = tk.StringVar()
self.nowGdate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGdate).grid(row=2, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Date').grid(row=3, column=0)
self.nowGtime = tk.StringVar()
self.nowGtime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGtime).grid(row=3, column=1)
tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)
self.gettime()
pass
def gettime(self):
gdt, gtm, ldt, ltm = GetDateTime()
gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'
ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]
self.nowGtime.set(gdt)
self.nowGdate.set(gtm)
self.LocalTime.set(ldt)
self.LocalDate.set(ltm)
self.after(100, self.gettime)
#print (ltm) # Prove it is running this and the external code, too.
pass
root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)
w = 200 # width for the Tk root
h = 125 # height for the Tk root
# get display screen width and height
ws = root.winfo_screenwidth() # width of the screen
hs = root.winfo_screenheight() # height of the screen
# calculate x and y coordinates for positioning the Tk root window
#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)
#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35 # -35 fixes it, more or less, for Win10
#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.mainloop()