Python 如何将进度条连接到函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15323574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to connect a progress bar to a function?
提问by user2148781
I'm trying to connect a progress bar to a function for my project.
我正在尝试将进度条连接到我的项目的功能。
This is what I have so far but im pretty sure it does nothing:
这是我到目前为止所拥有的,但我很确定它什么都不做:
def main():
pgBar.start()
function1()
function2()
function3()
function4()
pgBar.stop()
Here is the code where I make my progress bar if that helps at all:
这是我制作进度条的代码,如果有帮助的话:
pgBar = ttk.Progressbar(window, orient = HORIZONTAL, length=300, mode = "determinate")
pgBar.place(x=45, y=130)
I have been doing some research and understand that the tkinter window freezes when running a function or something like that. Is there a way I could "unfreeze" the window at the end of each function that is called inside the main one?
我一直在做一些研究,并了解到 tkinter 窗口在运行函数或类似的东西时会冻结。有没有办法可以在主函数内部调用的每个函数结束时“解冻”窗口?
回答by A. Rodas
Since tkinter is single threaded, you need another thread to execute your mainfunction without freezing the GUI. One common approach is that the working thread puts the messages into a synchronized object (like a Queue), and the GUI part consumes this messages, updating the progress bar.
由于 tkinter 是单线程的,您需要另一个线程来执行您的main函数而不冻结 GUI。一种常见的方法是工作线程将消息放入同步对象(如 a Queue),GUI 部分使用这些消息,更新进度条。
The following code is based on a full detailed exampleon ActiveState:
以下代码基于ActiveState上的完整详细示例:
import tkinter as tk
from tkinter import ttk
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.listbox = tk.Listbox(self, width=20, height=5)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300, mode='determinate')
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.listbox.pack(padx=10, pady=10)
self.progressbar.pack(padx=10, pady=10)
self.button.pack(padx=10, pady=10)
def spawnthread(self):
self.button.config(state="disabled")
self.thread = ThreadedClient(self.queue)
self.thread.start()
self.periodiccall()
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, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 5):
time.sleep(2)
msg = "Function %s finished..." % x
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
Since the original exampleon ActiveState is a bit messy IMO (the ThreadedClientis quite coupled with the GuiPart, and things like controlling the moment to spawn the thread from the GUI are not as straightforward as they could be), I have refactored it and also added a Button to start the new thread.
由于ActiveState 上的原始示例在 IMO 上有点混乱(它ThreadedClient与GuiPart, 以及控制从 GUI 生成线程的时间之类的事情并不像它们可能那样简单),因此我对其进行了重构并添加了一个按钮启动新线程。
回答by Honest Abe
To understand the 'freezing' you need to understand mainloop(). Calling this method starts the tkinterevent loop. The main thread is responsible for this loop. Therefore, when your work intensive function runs in the main thread, it is also interfering with the mainloop. To prevent this you can use a secondary Threadto run your function. It's recommended that secondary threads are not given access to tkinter objects. Allen B.Taylor, author of mtTkinter, states:
要了解“冻结”,您需要了解mainloop(). 调用此方法会启动tkinter事件循环。主线程负责这个循环。因此,当您的工作密集型函数在主线程中运行时,它也会干扰主循环。为了防止这种情况,您可以使用辅助Thread来运行您的功能。建议不要授予辅助线程访问 tkinter 对象的权限。艾伦B.Taylor,作者mtTkinter,指出:
The problems stem from the fact that the _tkinter module attempts to gain control of the main thread via a polling technique when processing calls from other threads. If it succeeds, all is well. If it fails (i.e., after a timeout), the application receives an exception with the message: "RuntimeError: main thread is not in main loop".
问题源于这样一个事实,即 _tkinter 模块在处理来自其他线程的调用时试图通过轮询技术获得对主线程的控制。如果成功,一切都会好起来的。如果失败(即超时后),应用程序会收到一个异常消息:“RuntimeError:主线程不在主循环中”。
You can have the secondary thread put information into a Queue. Then have a function that checks the Queueevery xmilliseconds, within the mainloop, via the after()method.
您可以让辅助线程将信息放入Queue. 然后有一个函数,通过该方法,在主循环内每x毫秒检查一次队列。after()
First, decide what you want the value of the Progressbar's maximumoption to be.
This is the Progressbar's maximum indicator value (how many units are required to fill the Progressbar).
For example, you could set maximum=4and then put the appropriate indicator value into the Queue after each of your four functions. The main thread can then retrieve these values (from the Queue) to set the progress via a tkinter.IntVar().
(Note that if you use progbar.step(), the Progressbar resets to 0 (empty) at the end, instead of reaching 4 (completely filled).)
首先,确定您希望Progressbar的最大值选项的值是多少。
这是进度条的最大指标值(填充进度条需要多少个单位)。例如,您可以maximum=4在四个函数中的每一个之后设置适当的指标值,然后将其放入队列中。然后主线程可以检索这些值(从队列中)以通过tkinter.IntVar(). (请注意,如果您使用progbar.step(),Progressbar 会在最后重置为 0(空),而不是达到 4(完全填满)。)
Here's a quick look at how you can use a tkinter.IntVar()with a Progressbar:
快速浏览一下如何将 atkinter.IntVar()与 Progressbar 一起使用:
int_var = tkinter.IntVar()
pb_instance = ttk.Progressbar(root, maximum=4)
pb_instance['variable'] = int_var
pb_instance.pack()
# completely fill the Progressbar
int_var.set(4)
# get the progress value
x = int_var.get()
Here's an example based on your own (renamed the "main" function "arbitrary"):
这是一个基于您自己的示例(将“main”函数重命名为“arbitrary”):
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
import Queue as queue
else:
from tkinter import ttk
import queue
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.int_var = tkinter.IntVar()
progbar = ttk.Progressbar(self.root, maximum=4)
# associate self.int_var with the progress value
progbar['variable'] = self.int_var
progbar.pack()
self.label = ttk.Label(self.root, text='0/4')
self.label.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.int_var.set(0) # empty the Progressbar
self.label['text'] = '0/4'
# create then start a secondary thread to run arbitrary()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
# check the Queue in 50ms
self.root.after(50, self.check_que)
def check_que(self):
while True:
try: x = que.get_nowait()
except queue.Empty:
self.root.after(25, self.check_que)
break
else: # continue from the try suite
self.label['text'] = '{}/4'.format(x)
self.int_var.set(x)
if x == 4:
self.b_start['state'] = 'normal'
break
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
que.put(1)
func_b()
que.put(2)
func_c()
que.put(3)
func_d()
que.put(4)
que = queue.Queue()
gui = GUI_Core() # see GUI_Core's __init__ method
gui.root.mainloop()
If all you want is something that indicates to the user that there is activity
you can set the Progressbar's modeoption to 'indeterminate'.
The indicator bounces back and forth in this mode (the speed relates to the maximum option).
如果您想要的只是向用户表明有活动,
您可以将 Progressbar 的模式选项设置为'indeterminate'。
指示器在此模式下来回弹跳(速度与最大选项有关)。
Then you can call the Progressbar's start()method directly before starting the secondary thread;
and then call stop()after secondary_thread.is_alive()returns False.
然后就可以start()在启动辅助线程之前直接调用Progressbar的方法;
然后stop()在secondary_thread.is_alive()返回False后调用。
Here's an example:
下面是一个例子:
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
func_b()
func_c()
func_d()
gui = GUI_Core()
gui.root.mainloop()
回答by Alok
You must be using:
self.pgBar.step(x)where 'x' is the amount to be increased in progressbar.
for this to get updated in your UI you have to put
self.window.update_idletasks()after every self.pgBar.step(x)statement
您必须使用:
self.pgBar.step(x)其中 'x' 是要在进度条中增加的数量。为了在您的 UI 中更新,您必须self.window.update_idletasks()在每条self.pgBar.step(x)语句之后放置

