【问题标题】:Change tkinter Label in other class?在其他类中更改 tkinter 标签?
【发布时间】:2017-07-30 18:41:32
【问题描述】:

有人可以帮我吗,我正在做一个关于课堂和在其他线程上运行任务的练习,然后是 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()

【问题讨论】:

  • tkinter 本身并不支持多线程。只有主线程可以调用它来更新 GUI——所以当你可以使用线程时,你需要注意这个限制和围绕它的代码。

标签: python multithreading tkinter


【解决方案1】:

正如我在评论中所说,tkinter 本身不支持多线程,但你可以做到这一点,只要只有一个线程,通常是主线程,使用(或“谈话” ) 它。

如果您想影响 GUI 显示的内容,其他线程必须以某种方式与 GUI 线程通信。这通常通过queue.Queue 来完成,但在这个相对简单的情况下,它可以通过global 变量来完成只要通过某种方式控制对它的并发访问——共享内存空间(即全局变量)是多线程与多任务的优势之一,但必须正确完成。

共享此类资源的一种简单方法是使用专用于该目的的threading.Lock。 (有关更多详细信息,请参阅维基百科文章 Lock (computer science)。) 所有对该共享资源(running 标志)的引用只能在“获取”Lock 并在之后“释放”它之后完成。幸运的是,使用 Python with 语句(如下所示)执行此操作很简单。

多线程问题的另一个关键方面是如何处理两个线程之间交换的任何信息。在这种情况下,我选择将 tkinter 线程 poll 设置为运行标志,观察变化,并相应地更新任何受影响的小部件。这可以通过使用通用小部件方法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()

【讨论】:

  • 感谢您提供清晰的信息和可能的解决方案。我将不得不更深入地处理线程。是导致我的循环运行速度变慢的延迟吗?这是我可以避免的事情还是我想要在我的代码中做的事情?
  • 您在更改 status_text 后没有使用 .pack(),是因为您每 100 毫秒更新一次整个帧吗?这种更新框架是我在每个使用“交互式”tkinter 框架的程序中更好地使用的东西吗?我刚刚看到你在循环中使用的 .sleep 语句来减慢它的速度,没有看到。
  • 我故意在for 循环中添加了对sleep() 的调用,该调用位于App2.run() 方法中 减慢它的速度,我看到你已经想通了。您只需 pack() 一个小部件一次。之后,您可以随时使用其config() 方法调整其设置。多久(更新速度)取决于您。我选择了 100 毫秒,因为它小于我添加到线程中的“for”循环中的 0.25 秒延迟(因此它会始终跟上它)。
  • 很高兴听到...在这种情况下,请接受我的回答。请参阅What should I do when someone answers my question? 此外,尽可能始终使用此技术。您总是必须做一些事情来处理线程之间的任何信息交换。 Lock 只是这样做的一种方法,这已经足够了。对于其他类型的数据,Queue 可能是更好的选择——具体取决于您正在做什么以及信息的类型和数量。
猜你喜欢
  • 2021-07-16
  • 2013-05-07
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多