如何在 Tkinter 的事件循环中运行自己的代码?

我弟弟刚刚开始编程,为了他的科学展项目,他正在模拟天空中的一群鸟。他已经写好了大部分代码,运行良好,但是鸟儿需要移动 每时每刻

然而,Tkinter 占用了自己事件循环的时间,因此他的代码不会运行。执行 root.mainloop()运行、运行并保持运行,它运行的唯一内容是事件处理程序。

有没有一种方法可以让他的代码沿着主循环运行(没有多线程,这是令人困惑的,这应该保持简单) ,如果有,是什么?

现在,他想出了一个难看的黑客技术,将他的 move()函数绑定到 <b1-motion>,只要他按住按钮并摆动鼠标,它就能工作。但肯定有更好的办法。

204491 次浏览

Tk对象使用 after方法:

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."""

另一种选择是让 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 的事件循环。

解决方案由比约恩发布在我的计算机上产生一条“ RuntimeError: 从不同的公寓调用 Tcl”消息(RedHat Enterprise 5,python 2.6.1)。Bjorn 可能没有收到这条消息,因为根据 我查了一个地方,使用 Tkinter 进行错误的线程处理是不可预测的,并且依赖于平台。

问题似乎是 app.start()算作对 Tk 的引用,因为应用程序包含 Tk 元素。我通过在 __init__中用 self.start()代替 app.start()来修复这个问题。我还让所有的 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所执行的操作: 用您的更改更新窗口,但是您在自己的循环中执行。

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


while 1:
task()

这是 GPS 阅读器和数据显示器的第一个工作版本。Tkinter 是一个非常脆弱的东西,只有很少的错误消息。它不把东西,并没有告诉为什么大多数时间。很难从一个好的所见即所得的表单开发人员。无论如何,这将运行一个小例程,每秒10次,并在表单上显示信息。花了点时间才成功。当我尝试一个计时器值为0时,表单永远不会出现。我的头好痛!每秒10次以上对我来说已经足够了。我希望能帮到别人。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()