2017-07-30 88 views
1

有人可以幫助我,我正在做一個關於課程的練習,然後在其他線程上運行任務然後tkinter。我想改變另一個班的標籤。無法讓我的腳本工作。在其他課上更改tkinter標籤?

我嘗試不同的東西,但我有一些麻煩與理解,從類和線程的繼承,所以這只是更多地瞭解它的一個例子。

from tkinter import * 
import tkinter as tk 
from tkinter import ttk 
import threading 

#Gloabl for stopping the run task 
running = True 

#class 1 with window 
class App(): 

    def __init__(self): 
      #making the window 
      self.root = tk.Tk() 
      self.root.geometry("400x400+300+300") 
      self.root.protocol("WM_DELETE_WINDOW", self.callback) 
      self.widgets() 
      self.root.mainloop() 

    # stop task and close window 
    def callback(self): 
      global running 
      running = False 
      self.root.destroy() 

    # all the widgets of the window 
    def widgets(self): 
      global labelvar 
      #startbutton 
      self.start_button = tk.Button(self.root, text="Start",  command=lambda:App2()) 
      self.start_button.pack() 

      #stopbutton 
      self.stop_button = tk.Button(self.root, text="Stop", command=lambda:self.stop()) 
      self.stop_button.pack() 

      #Defining variable for text for label 
      labelvar = "Press start to start running" 
      self.label = tk.Label(self.root, text=labelvar) 
      self.label.pack() 

    #stop the task 
    def stop(self): 
      global running 
      running = False 


#class 2 with task in other thread 
class App2(threading.Thread): 

    def __init__(self): 
      global running 
      #check if task can be run 
      running = True 
      threading.Thread.__init__(self) 
      self.start() 

    def run(self): 
       #starting random work 
       for i in range(10000): 
        print(i) 
        labelvar = "running" 
        App.label.pack() 
        #checking if task can still be running else stop task 
        if running == False: 
         break 
         labelvar = "stopped" 
         App.label.pack() 

#initiate main app 
app = App() 
+0

'tkinter'本質上不支持多線程。只有主線程可以調用它來更新GUI,因此,當您可以使用線程時,您需要注意圍繞它的限制和代碼。 – martineau

回答

0

正如我在評論說,tkinter不支持多線程本身,而是你可以做到這一點,只要只有一個線程,通常是主要的一個,使用(或「談話」)吧。

如果你想影響什麼的GUI顯示,其它線程(S)必須以某種方式與GUI線程通信。這通常是通過一個queue.Queue完成,但在這種相對簡單的情況下,它可以通過一個global可變進行提供給它的併發訪問是由一些裝置共享存儲器空間(即全局變量)控制是的優點之一多線程與多任務處理,但必須正確完成和完成。

一種簡單的方式來分享這樣的資源是通過使用專用的用於該目的threading.Lock。 (詳情請參見維基百科的文章Lock (computer science)) 這個共享資源的所有引用(在running標誌)只應「獲得」的Lock和「釋放」它後來經過。幸運的是,使用Python with語句(如下所示)來做到這一點是微不足道的。

多線程問題的另一個重要方面是任何信息的兩個線程被處理之間如何交換。在這種情況下,我選擇使tkinter線程輪詢運行標誌,觀察更改並相應地更新任何受影響的小部件。這可以通過使用通用小部件方法after()來完成,該方法告知tkinter將未來調用(在'主循環'內)調度爲用戶提供的函數或方法,並傳遞給它某些參數。爲了讓這種情況重複發生,被調用的函數可以重新調度本身在完成之前通過調用after()重新運行。

下面是您的代碼的修改版本,它可以完成這些工作。請注意,App2從不呼叫tkinter或觸摸它的任何小部件,這就是它工作的原因。

import threading 
from time import sleep 
from tkinter import * 
import tkinter as tk 
from tkinter import ttk 

DELAY = 100 # millisecs between status label updates 

# global flag and a Lock to control concurrent access to it 
run_flag_lock = threading.Lock() 
running = False 


# class 1 with window 
class App(): 
    def __init__(self): 
     global running 
     self.root = tk.Tk() 
     self.root.geometry("400x400+300+300") 
     self.root.protocol("WM_DELETE_WINDOW", self.quit) 
     self.create_widgets() 
     with run_flag_lock: 
      running = False 
     self.root.after(DELAY, self.update_status, None) # start status widget updating 
     self.root.mainloop() 

    # create all window widgets 
    def create_widgets(self): 
     self.start_button = tk.Button(self.root, text="Start", command=self.start) 
     self.start_button.pack() 

     self.stop_button = tk.Button(self.root, text="Stop", command=self.stop) 
     self.stop_button.pack() 

     self.status_label = tk.Label(self.root, text='') 
     self.status_label.pack() 

    def update_status(self, run_state): 
     """ Update status label text and state of buttons to match running flag. """ 
     # no need to declare run_flag_lock global since it's not being assigned a value 
     with run_flag_lock: 
      if running != run_state: # status change? 
       if running: 
        status_text = 'Press Stop button to stop task' 
        run_state = True 
       else: 
        status_text = 'Press Start button to start task' 
        run_state = False 
       self.status_label.config(text=status_text) 
       # also update status of buttons 
       if run_state: 
        self.start_button.config(state=DISABLED) 
        self.stop_button.config(state=ACTIVE) 
       else: 
        self.start_button.config(state=ACTIVE) 
        self.stop_button.config(state=DISABLED) 

     # run again after a delay to repeat status check 
     self.root.after(DELAY, self.update_status, run_state) 

    # start the task 
    def start(self): 
     global running 
     with run_flag_lock: 
      if not running: 
       app2 = App2() # create task thread 
       app2.start() 
       running = True 

    # stop the task 
    def stop(self): 
     global running 
     with run_flag_lock: 
      if running: 
       running = False 

    # teminate GUI and stop task if it's running 
    def quit(self): 
     global running 
     with run_flag_lock: 
      if running: 
       running = False 
     self.root.destroy() 


# class 2 with task in another thread 
class App2(threading.Thread): 
    def __init__(self): 
     super(App2, self).__init__() # base class initialization 
     self.daemon = True # allow main thread to terminate even if this one is running 

    def run(self): 
     global running 
     # random work 
     for i in range(10000): 
      print(i) 
      # Normally you shouldn't use sleep() in a tkinter app, but since this is in 
      # a separate thread, it's OK to do so. 
      sleep(.25) # slow printing down a little 
      # stop running if running flag is set to false 
      with run_flag_lock: 
       if not running: 
        break # stop early 

     with run_flag_lock: 
      running = False # task finished 

# create (and start) main GUI app 
app = App() 
+0

感謝您提供清晰的信息和可能的解決方案。我將不得不深入線程。它是否導致我的循環運行速度變慢?那是我可以避免的,還是我想在我的代碼中想要的東西? – Bart1986

+0

更改status_text後,您沒有使用.pack(),是因爲您每100毫升更新整個幀?這種更新框架是什麼,我最好在每個程序中使用一種'交互式'tkinter框架?我剛看到你在循環中使用的.sleep語句來減慢速度,沒有看到。 – Bart1986

+0

我有意地在'App2.run()'方法中的'for'循環內添加了一個對'sleep()'的調用,以減慢它的速度,我發現你已經想通了。你只需「包裝()」一次。之後,您可以隨時通過使用其「config()」方法來調整其設置。多久(更新速度)取決於您。我選擇了100毫秒,因爲它小於。延遲了25秒,我加入了線程中的'for'循環(所以它總是跟上它)。 – martineau