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

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

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

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

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

Ответ 1

Используйте метод 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."""

Ответ 2

Решение, отправленное Bjorn, приводит к сообщению "RuntimeError: Calling Tcl из разных квартир" на моем компьютере (RedHat Enterprise 5, python 2.6.1). Бьорн, возможно, не получил этого сообщения, так как, согласно одному месту, которое я проверил, неправильное использование Threading с 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)

Ответ 3

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

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

while 1:
   task()  

Ответ 4

Другой вариант - позволить 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.

Ответ 5

Это первая рабочая версия того, что будет устройством чтения 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()