将线程进度回传到tkinter框架

问题描述 投票:0回答:1

我将其发布为参考,供那些寻求将线程进度传达回tkinter框架或窗口的人使用。我已经在SO和其他站点中看到了几种详细的方法,但是对我来说似乎还没有一种足够的方法。因此,这是一种在消息框更新和“缩放小部件”前进时显示进度的方法。它使用tkinter变量类StringVar和DoubleVar,而不是尝试使用回调或在主线程中连续轮询队列。

当然,欢迎发表评论,但这种方法似乎效果很好。

`

import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time


class App(tk.Tk):

    def __init__(self):
      tk.Tk.__init__(self)
      self.queue = queue.Queue()
      self.msgCt=0
      self.listbox = tk.Listbox(self, width=20, height=5)
      self.scaleVal=tk.DoubleVar()
      self.progressbar = ttk.Scale(self, orient='horizontal',
                                         length=300,
                                         from_=0.0, to=100.0,
                                         variable=self.scaleVal)
      self.scaleVal.set(0.0)
      self.button = tk.Button(self, text="Start", command=self.spawnthread)
      self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
      self.msgTxt=tk.StringVar(self,"Messages Here...")
      self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
                             anchor=NW,relief=GROOVE)

      self.listbox.grid(row=0,column=0,columnspan=2)
      self.msgBox.grid(row=1,column=0,columnspan=2)
      self.progressbar.grid(row=2,column=0,columnspan=2)
      self.button.grid(row=3,column=0)
      self.msgBtn.grid(row=3,column=1)

    def spawnthread(self):
      self.button.config(state="disabled")
      self.listbox.delete(0, END)
      self.thread = ThreadedClient(self.queue,self.msgTxt,self.scaleVal)
      self.thread.start()
      self.periodiccall()

    def sendMessage(self,msg=None):
      if not msg==None:
        self.msgTxt.set(msg)
      else:
        self.msgTxt.set("Message {}".format(self.msgCt))
      self.msgCt+=1

    def periodiccall(self):
        self.checkqueue()
        if self.thread.is_alive():
            self.after(100, self.periodiccall)
        else:
            self.button.config(state="active")

    def checkqueue(self):
        while self.queue.qsize():
            try:
                msg = self.queue.get(0)
                self.listbox.insert('end', msg)
                # self.progressbar.step(25)
            except queue.Empty:
                pass


class ThreadedClient(threading.Thread):

    def __init__(self, qu, mtxt,dvar):
        threading.Thread.__init__(self)
        self.queue = qu
        self.msgTxt=mtxt
        self.scaleVal=dvar

    def run(self):
      self.scaleVal.set(0.0)
      for x in range(1, 10):
          time.sleep(2)
          msg = "Function %s finished..." % x
          self.msgTxt.set(msg)
          self.scaleVal.set(x*10)
          self.queue.put(msg)


if __name__ == "__main__":
    app = App()
    app.mainloop()

`

python multithreading tkinter
1个回答
0
投票

作为对几条评论的回应,这是代码的新版本:

import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
from pickle import FALSE


class App(tk.Tk):

    def __init__(self):
      tk.Tk.__init__(self)
      self.msgCt=0
      self.thrdCt=0;
      self.scaleVal=tk.DoubleVar()
      self.doneSignal=tk.BooleanVar()
      self.doneSignal.set(False)
      self.doneSignal.trace("w",self.on_doneSignal_set)
      self.progressbar = ttk.Progressbar(self, orient='horizontal',
                                         length=300,
                                         maximum=100.0,
                                         variable=self.scaleVal)
      self.scaleVal.set(0.0)
      self.startBtn = tk.Button(self, text="Start", command=self.spawnthread)
      self.stopBtn=tk.Button(self,text="Stop", command=self.stopthread)
      self.stopBtn.config(state="disabled")
      self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
      self.msgTxt=tk.StringVar(self,"Messages Here...")
      self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
                             anchor=NW,relief=GROOVE)

      self.msgBox.grid(row=0,column=0,columnspan=3)
      self.progressbar.grid(row=1,column=0,columnspan=3)
      self.startBtn.grid(row=2,column=0)
      self.stopBtn.grid(row=2,column=1)
      self.msgBtn.grid(row=2,column=2)

    def on_doneSignal_set(self,*kwargs):
      self.sendMessage("Thread is DONE")
      self.startBtn.config(state="active")
      self.stopBtn.config(state="disabled")

    def stopthread(self):
      if self.thread.is_alive():
        self.thread.stopNow()

    def spawnthread(self):
      self.thrdCt=0
      self.startBtn.config(state="disabled")
      self.stopBtn.config(state="active")
      self.thread = ThreadedClient(self.msgTxt,self.scaleVal,self.doneSignal)
      self.thread.start()
      # self.periodiccall()

    def sendMessage(self,msg=None):
      if not msg==None:
        self.msgTxt.set(msg)
      else:
        self.msgTxt.set("Message {}".format(self.msgCt))
      self.msgCt+=1

class ThreadedClient(threading.Thread):

    def __init__(self, mtxt,dvar,dsig):
      threading.Thread.__init__(self)
      self.msgTxt=mtxt
      self.scaleVal=dvar
      self._stopNow=False
      self._doneSignal=dsig
      self._lock=threading.Lock()

    def run(self):
      self._stopNow=False
      self.scaleVal.set(0.0)
      for x in range(1, 10):
        if not self.checkStopNow():
          time.sleep(2)
          msg = "Function %s finished..." % x
          self.msgTxt.set(msg)
          self.scaleVal.set(x*10)
        else:
          break
      self._doneSignal.set(True)

    def stopNow(self):
      with self._lock:
        self._stopNow=True

    def checkStopNow(self):
      rtrn=False
      with self._lock:
        rtrn=self._stopNow
      return rtrn

if __name__ == "__main__":
    app = App()
    app.mainloop()

首先,本练习的动机:我有一个大型python应用程序,该应用程序使用Scipy.optimize查找建模问题的解决方案。这通常可能会花费很长时间,因此我想要一种使其运行的方法,但是要定期向用户发布消息,以使他们知道正在发生的事情,允许用户在中间中止操作,最后将消息发布给主体现在完成建模的线程。我的原始代码部分基于一个线程示例,该示例假定了生产者/消费者线程模型,生产者通过该模型创建数据(放入队列),然后消费者使用它。这是我遇到的问题的错误模型,因此此新代码没有队列。它只是使用run()方法模拟了一个较长的建模过程,其中有对sleep()的调用,显然可以由SciPy.minimize函数调用中的步骤代替。

此新示例使用DoubleVar允许线程更新progressBar(为此建议使用stovfl),使用StringVar从建模线程更新主线程中的消息框,最后使用BooleanVar通知主线程:事情完成了。此版本在主线程中没有轮询。对我来说,这似乎不是一个很好的解决方案!

我怎么知道对DoubleVar,StringVar和BooleanVar的更改会进入主线程?只有该程序有效!!并不是可以从建模线程或使用主GUI线程中的按钮来更新消息框。

再次,欢迎发表评论-请告诉我该方法不起作用的原因,然后告诉我为什么有这些原因!这是否违反了Python的某些基本设计,还是会因为某种原因而无法正常使用?

© www.soinside.com 2019 - 2024. All rights reserved.