2013-05-31 42 views
2

我在使用Tkinter(和ttk)和Python將GUI組織成可用且不是超級錯誤的程序時遇到了麻煩。基本上,它只是將圖像從網上下載,但即使是一個簡單的GUI,我也遇到了麻煩。雖然所有的東西都在控制檯中工作,但使GUI成爲一場噩夢,更不用說使其工作。現在,我有它的工作,但它經常崩潰,很明顯,我做錯了錯誤獲取GUI中的變量錯誤沒有被正確訪問(甚至控制檯中的錯誤消息,我已經把自己的功能,以確保事情去正確)和不斷崩潰。如何組織線程化GUI應用程序(Python)

基本上我有這樣的事情。

發生並需要工作的主要事情是:從輸入文本中的用戶輸入字符串發送到程序的密集部分(當前包含在線程中),強化部分步進GUI的進度條,以及密集部分發送文本消息到文本框/記錄器,無需GUI和密集部件崩潰。密集的部分應該在GUI完全加載後立即啓動,並在準備就緒時將啓動消息發送到文本框。

密集部分處理其他事情,但不干擾GUI,例如實際下載和保存圖像,瀏覽和文件I/O,而且大多數情況下我都沒有問題。

我也讀過關於隊列和線程和教程,但我似乎無法得到它。尤其是如何讓程序不斷地在GUI中執行進度條,同時還將文本消息發送到GUI(例如,我甚至可以從隊列接近,而不必執行非常慢且CPU密集型的工作,而如果循環和多個隊列使得它更加瘋狂,簡單的例子中,只需要一個簡單的while和queue.get()等待,因爲它消耗的資源很少)。所以我的問題是,我需要採取什麼樣的結構來實現這個目標,如果可能的話,我可以得到一個或兩個示例(我從示例中學到的東西比從閱讀文檔中學得更好)?非常感謝你。

from Tkinter import * 
import ttk 
import Threading 
import #a bunch of other stuff 

class myHardWorkerThread (threading.Thread): 
    def __init__(self): 
     threading.Thread.__init__(self) 
     self.setDaemon(True) 
     self.myClass = ModifiedConsoleClass() 

    def run(self): 
      #thread needs to wait at least a little otherwise the thread begins too 
      #fast and causes even more errors, probably due to it sending text to 
      #the textbox upon startup among other things and just overall no 
      #organization 
      time.sleep(3) 
      self.myClass.BeginDoingStuff() 

class ApplyMyGuiAndStartThread(): 
    def __init__(self, root, thread): 

     root.geometry('500x500') 
     root.resizable(0,0) 

     #Put backgrounds or images or logos here 
     self.canvas = Canvas(root) 
     self.canvas.pack() 

     #My textbox that acts like a Log/Console output 
     self.txtLogger = Text(root,state="disabled",wrap="word") 
     self.txtLogger.place() 
     self.scrollbar = Scrollbar(root) 
     self.scrollbar.place() 

     #Progressbar 
     self.myVal = IntVar() 
     self.TProgressbar = ttk.Progressbar(root, orient=HORIZONTAL, variable = self.myVal, mode='determinate') 
     self.TProgressbar.place() 

     #Entrybox for user input 
     self.txbEntryText = StringVar() 
     self.txtbEntry = ttk.Entry (root, textvariable=self.txbEntryText) 
     self.txtbEntry.place() 
     self.txtbEntry.bind("<Return>", self.SendFromGUItoThread) 

     self.thread = thread 
     self.thread.start() 

    def SendFromGUItoThread(self,arg=None): 

     myentry = str(self.txtbEntry.get()) 
     self.txtbEntry.delete(0, END) 
     self.thread.myClass.entryBoxValueCopy = myentry 


    def SendFromThreadToGUI(self,msg): 
     try: 
      self.txtLogger['state'] = 'normal' 
      self.txtLogger.insert('end', msg) 
      self.txtLogger['state'] = 'disabled 
     except: 
      print "Could not be printed" 


class ModifiedConsoleCode(): 
    def __init__(self): 
     #constants here like 
     self.entryBoxValueCopy = None 

    def BeginDoingStuff(): 
     #Thread does the bulk of work here, includes connecting to websites, 
     #collecting info, sending text messages to GUI, downloading images and 
     #stepping the progressbar by a calculated amount by file size 

    def OneOfManyFunctionsUsedInsideBeginDoingStuff(): 
     #Breaks things down like looping time.sleep waits for user input in the entry box 
     #showing up in entryBoxValueCopy, and using the user input to surf websites and 
     #collect images 

if __name__ == '__main__': 

     root = Tk() 
     root.title(titleOfTheProgram) 

     worker = myHardWorkerThread() 

     w = ApplyMyGuiAndStartThread(root,worker) 

     root.mainloop() 
     os._exit(0) 

回答

1

而不是使用線程,你應該使用tkinter方法「after」在tkinter事件循環上設置一個事件。

使用canvas元素時,比如我會用

canvar.after(50, func=keepDoingSomething) 

這個工作原理類似於的javascript功能setTimeout和它是線程安全的,並用Tkinter的GUI線程不會干涉。

+0

在這種情況下,我不認爲這會有所幫助 - 問題意味着需要完成一些長時間運行的任務,例如下載圖像。與主循環中的時間片相比,用線程更容易。 –

3

簡而言之,您無法與工作線程中的小部件進行交互。你唯一的選擇是讓你的工作線程在線程安全隊列上推送一些東西,並讓主線程輪詢它。

您不需要任何while循環來輪詢隊列。你已經有一個無限循環 - 事件循環(例如:mainloop) - 所以不需要添加額外的循環。

輪詢從主線程隊列的方式看起來是這樣的:

def pollQueue(self): 
    <look at the queue, act on the results> 
    self.after(100, self.pollQueue) 

這樣做是整理對隊列的每100毫秒。當然,您可以將輪詢間隔設置爲任何你想要的。