2009-01-19 53 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() 
相關問題