2009-01-19 26 views
83

我的小弟弟刚刚进入编程阶段,为了他的科学博览会项目,他正在模拟天空中的一群鸟。他的大部分代码都已经写好了,而且效果很好,但是鸽子需要每次移动。然而,Tkinter为自己的事件循环耗费了时间,所以他的代码将不会运行。做root.mainloop()运行,运行,并继续运行,它唯一运行的事件处理程序。你如何在Tkinter的事件循环旁边运行自己的代码?

有没有办法让他的代码与主循环一起运行(没有多线程,它很混乱,应该保持简单),如果是这样,它是什么?

现在,他想出了一个丑陋的黑客,将他的move()函数绑定到<b1-motion>,这样只要他按下按钮并摆动鼠标,它就可以工作。但是有一个更好的方法。

回答

105

使用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.""" 
+19

如果您指定超时时间为0,任务将在完成后立即重新放回事件循环。这将不会阻止其他事件,同时仍然尽可能经常运行您的代码。 – Nathan 2009-09-09 03:27:05

+0

把我的头发拉出几小时后,试图让opencv和tkinter正确地一起工作,并干净地关闭[X]按钮被点击时,这与win32gui.FindWindow(None,'窗口标题')​​一起诀窍!我是这样一个小白;-) – JxAxMxIxN 2016-10-16 14:48:34

2

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

+2

只需使用队列来与线程进行通信。 – jldupont 2011-08-12 11:40:15

+2

不确定这可以工作。只是尝试了类似的东西,我得到“RuntimeError:主线程不在主循环中”。 – jldupont 2011-08-12 11:56:28

35

Bjorn发布的解决方案导致我的计算机(RedHat Enterprise 5,python 2.6.1)出现“RuntimeError:调用 来自不同公寓的Tcl”消息。 Bjorn可能没有收到这个消息,因为根据one place I checked,对Tkinter的线程处理不当是不可预测的,并且取决于平台。

问题似乎是app.start()作为Tk的参考,因为应用程序包含Tk元素。我通过在__init__内替换app.start()self.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) 
15

当自己写循环,如模拟(我认为),你需要调用update功能它执行mainloop做什么:更新窗口随着你的改变,但你在你的循环中做。

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

while 1: 
    task() 
2

这是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() 
相关问题