我将其发布为参考,供那些寻求将线程进度传达回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()
`
作为对几条评论的回应,这是代码的新版本:
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的某些基本设计,还是会因为某种原因而无法正常使用?