Como você executar seu próprio código ao lado de ciclo de eventos do Tkinter?
Pergunta
Meu irmão mais novo está apenas começando na programação, e para o seu projeto da feira de ciência, ele está fazendo uma simulação de um bando de pássaros no céu. Ele obteve a maior parte do código escrito, e ele funciona muito bem, mas as aves precisa mover cada momento .
Tkinter, no entanto, hogs o tempo para o seu próprio ciclo de eventos, e assim seu código não será executado. Fazendo corridas root.mainloop()
, funcionamentos, e continua correndo, e a única coisa que funciona é os manipuladores de eventos.
Existe uma maneira de ter seu código executado ao lado do mainloop (sem multithreading, é confuso e este simples deve ser mantido), e em caso afirmativo, o que é?
Agora, ele veio com uma gambiarra, amarrando sua função move()
para <b1-motion>
, de modo que, enquanto ele segura o botão para baixo e mexe o mouse, ele funciona. Mas tem que haver uma maneira melhor.
Solução
Use o método after
no objeto 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()
Aqui está a declaração e documentação para o método 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."""
Outras dicas
A solução postado por resultados Bjorn em um "RuntimeError: Chamando Tcl de diferentes apartamento" mensagem no meu computador (RedHat empresa 5, pitão 2.6.1). Bjorn pode não ter começado esta mensagem, uma vez que, de acordo com a um lugar que eu verificadas , manuseio incorreto rosqueamento com Tkinter é imprevisível e dependente de plataforma.
O problema parece ser que as contagens app.start()
como uma referência para Tk, pois aplicativo contém elementos TK. Eu reparei isso substituindo app.start()
com um self.start()
__init__
dentro. Eu também fez com que todas as referências Tk são ou dentro do função que chama mainloop()
ou estão dentro funções que são chamadas por a função que as chamadas mainloop()
(isto é, aparentemente, fundamental para evitar o erro "diferente apartamento").
Finalmente, eu adicionei um manipulador de protocolo com uma chamada de retorno, uma vez que sem esta o programa termina com um erro quando a janela Tk é fechada pelo usuário.
O código revisto é a seguinte:
# 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)
Ao escrever seu próprio loop, como na simulação (presumo), você precisa chamar a função update
que faz o que o mainloop
faz:. Atualiza a janela com as alterações, mas você fazê-lo em seu loop
def task():
# do something
root.update()
while 1:
task()
Outra opção é deixar tkinter executar em um segmento separado. Uma maneira de fazer isso é assim:
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')
Tenha cuidado, porém, a programação multithreaded é difícil e é realmente fácil de atirar o seu auto no pé. Por exemplo, você tem que ter cuidado quando você alterar as variáveis ??de membro da classe de exemplo acima para que não interrompa o ciclo de eventos de Tkinter.
Esta é a primeira versão de trabalho do que será um leitor de GPS e apresentador de dados. tkinter é uma coisa muito frágil, com muito poucas mensagens de erro maneira. Não colocar coisas e não dizer por que a maior parte do tempo. Muito difícil vindo de um bom WYSIWYG desenvolvedor formulário. De qualquer forma, este dirige uma pequena rotina de 10 vezes por segundo e apresenta as informações em um formulário. Levou um tempo para que isso aconteça. Quando eu tentei um valor temporizador de 0, a forma nunca veio para cima. Minha cabeça agora dói! 10 ou mais vezes por segundo é bom o suficiente para mim. Espero que ajude alguém. 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()