2017-07-30 68 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