Как вы запускаете свой собственный код вместе с циклом событий Tkinter?

Мой младший брат только начинает заниматься программированием, и для своего проекта Science Fair он симулирует стаю птиц в небе. Он написал большую часть своего кода, и он прекрасно работает, но птицам нужно двигаться каждый момент.

Tkinter, однако, тратит время на собственный цикл обработки событий, поэтому его код не запускается. дела root.mainloop() работает, работает и продолжает работать, и единственное, что он запускает, это обработчики событий.

Есть ли способ заставить его код работать вместе с основным циклом (без многопоточности, это сбивает с толку, и это должно быть простым), и если да, то что это?

Прямо сейчас он придумал уродливый хак, завязав move() функция к <b1-motion>, так что пока он удерживает кнопку нажатой и покачивает мышь, все работает. Но должен быть лучший способ.

5 ответов

Решение

Использовать after метод на 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()

Вот декларация и документация для 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."""

Решение, опубликованное Bjorn, приводит к появлению на моем компьютере сообщения "RuntimeError: Вызов Tcl из другой квартиры" (RedHat Enterprise 5, python 2.6.1). Бьорн, возможно, не получил это сообщение, так как, согласно одному месту, которое я проверял, неправильное управление потоками с Tkinter непредсказуемо и зависит от платформы.

Кажется, проблема в том, что app.start() считается ссылкой на Tk, так как приложение содержит элементы Tk. Я исправил это, заменив app.start() с self.start() внутри __init__, Я также сделал так, чтобы все ссылки Tk были либо внутри функции, которая вызываетmainloop() или внутри функций, которые вызываются функцией, которая вызывает mainloop() (это, по-видимому, важно, чтобы избежать ошибки "другая квартира").

Наконец, я добавил обработчик протокола с обратным вызовом, так как без этого программа завершается с ошибкой, когда окно Tk закрывается пользователем.

Пересмотренный код выглядит следующим образом:

# 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)

При написании собственного цикла, как в симуляции (я полагаю), вам нужно вызвать update функция, которая делает то, что mainloop has: обновляет окно с вашими изменениями, но вы делаете это в цикле.

def task():
   # do something
   root.update()

while 1:
   task()  

Другой вариант - позволить tkinter выполняться в отдельном потоке. Один из способов сделать это так:

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')

Будьте осторожны, многопоточное программирование сложно, и действительно легко выстрелить себе в ногу. Например, вы должны быть осторожны при изменении переменных-членов приведенного выше примера класса, чтобы не прерывать цикл обработки событий Tkinter.

Это первая рабочая версия того, что будет считыватель GPS и предъявитель данных. tkinter - очень хрупкая вещь, слишком мало сообщений об ошибках. Это не складывает вещи и не говорит, почему большую часть времени. Очень сложно поступить от хорошего разработчика форм WYSIWYG. Во всяком случае, это выполняется небольшая процедура 10 раз в секунду и представляет информацию в форме. Потребовалось время, чтобы это произошло. Когда я пробовал значение таймера 0, форма никогда не появлялась. Моя голова сейчас болит! 10 или более раз в секунду мне достаточно. Я надеюсь, что это помогает кому-то еще. Майк Морроу

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()
Другие вопросы по тегам