Mon petit frère commence à peine à programmer et, dans le cadre de 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 bouger à chaque instant.
Tkinter, cependant, a besoin de sa propre boucle d’événement et son code ne fonctionnera pas. Faire root.mainloop()
s'exécute, s'exécute et continue à fonctionner, et les seuls gestionnaires exécutés sont les gestionnaires d'événements.
Y a-t-il un moyen de faire passer son code le long de la boucle principale (sans multithreading, c'est déroutant et cela devrait rester simple), et si oui, de quoi s'agit-il?
À l’heure actuelle, il a eu un vilain bidouillage, liant sa fonction move()
à <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.
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 pour la méthode after
:
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."""
Le solution publiée par Bjorn génère un message "RuntimeError: appeler Tcl de différents appartements" sur mon ordinateur (RedHat Enterprise 5, python 2.6.1). Bjorn pourrait pas eu ce message, car, selon n endroit que j'ai vérifié , une mauvaise gestion du threading 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 corrigé cela en remplaçant app.start()
par un self.start()
dans __init__
. J'ai également fait en sorte que toutes les références Tk soient soit à l'intérieur de la fonction qui appelle mainloop()
, soit à l'intérieur fonctions appelées par la fonction qui appelle mainloop()
(il est apparemment essentiel d'é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)
Lorsque vous écrivez votre propre boucle, comme dans la simulation (je suppose), vous devez appeler la fonction update
qui fait ce que fait le mainloop
: met à jour la fenêtre avec vos modifications, mais vous le faites dans ta 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 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 formulaires 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. Lorsque j'ai essayé une valeur de minuterie de 0, la forme n'a jamais été relevé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()