问题:您如何在Tkinter的事件循环中运行自己的代码?
我的弟弟刚刚开始编程,对于他的Science Fair项目,他正在模拟天空中一群鸟。他得到了大部分他写的代码,它工作得很好,但鸟儿需要移动的每一刻。
但是,Tkinter浪费了自己的事件循环的时间,因此他的代码无法运行。这样root.mainloop()
运行,运行,并保持运行,并且它运行的唯一事情是事件处理程序。
有没有一种方法可以让他的代码与mainloop一起运行(没有多线程,这很令人困惑,应该保持简单),如果这样,那是什么?
现在,他想出了一个丑陋的方法,将其move()
功能绑定到<b1-motion>
,这样,只要按住按钮并摇动鼠标,它就可以工作。但是必须有一个更好的方法。
回答 0
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."""
回答 1
Bjorn发布的解决方案在我的计算机(RedHat Enterprise 5,python 2.6.1)上显示“ RuntimeError:从不同的公寓呼叫Tcl”消息。Bjorn可能没有得到此消息,因为据我检查过的一个地方,使用Tkinter处理线程是不可预测的且依赖于平台。
问题似乎是可以将其app.start()
视为对Tk的引用,因为app包含Tk元素。我通过替换app.start()
为self.start()
inside来解决此问题__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)
回答 2
在编写自己的循环时,就像在模拟中一样(我假设),您需要调用执行update
功能的函数mainloop
:使用所做的更改来更新窗口,但是您需要在循环中进行操作。
def task():
# do something
root.update()
while 1:
task()
回答 3
另一个选择是让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的事件循环。
回答 4
这是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()