Como você executar seu próprio código ao lado de ciclo de eventos do Tkinter?

StackOverflow https://stackoverflow.com/questions/459083

  •  19-08-2019
  •  | 
  •  

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.

Foi útil?

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()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top