如何将进度条连接到函数?

时间:2013-03-10 14:55:30

标签: python tkinter

我正在尝试将进度条连接到我的项目的功能。

这是我到目前为止所做的,但我很确定它什么都不做:

def main():
    pgBar.start()
    function1()
    function2()
    function3()
    function4()
    pgBar.stop()

以下是我创建进度条的代码(如果有帮助的话):

pgBar = ttk.Progressbar(window, orient = HORIZONTAL, length=300, mode = "determinate")
pgBar.place(x=45, y=130)

我一直在做一些研究,并了解tkinter窗口在运行函数或类似的东西时会冻结。有没有办法可以在主要内部调用的每个函数的末尾“解冻”窗口?

3 个答案:

答案 0 :(得分:17)

由于tkinter是单线程,因此您需要另一个线程来执行main函数而不冻结GUI。一种常见的方法是工作线程将消息放入同步对象(如Queue),GUI部分使用此消息,更新进度条。

以下代码基于ActiveState上的完整详细example

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()

由于ActiveState上的original example有点混乱IMO(ThreadedClientGuiPart完全耦合,控制从GUI生成线程的时刻等事情并非如此尽可能直截了当,我已经重构了它,并添加了一个Button来启动新线程。

答案 1 :(得分:13)

要了解“冻结”,您需要了解mainloop()。调用此方法将启动 tkinter 事件循环。主线程负责此循环。因此,当您的工作密集型函数在主线程中运行时,它也会干扰主循环。为防止这种情况,您可以使用辅助Thread来运行您的功能。建议不要让辅助线程访问tkinter对象。 mtTkinter的作者Allen B.Taylor表示:

  

问题源于_tkinter模块尝试的事实   通过轮询技术获得主线程的控制权   处理来自其他线程的调用。    如果成功,一切都很顺利。如果失败(即超时后),则   应用程序收到带有消息的异常:“RuntimeError:   主线程不在主循环“。

您可以让辅助线程将信息放入Queue。然后有一个函数,通过after()方法检查主循环中每 x 毫秒的队列

首先,确定您希望进度条maximum选项的值是什么。
这是Progressbar的最大指标值(填充Progressbar需要多少单位)。 例如,您可以设置maximum=4,然后在四个函数中的每个函数之后将适当的指标值放入队列中。然后,主线程可以检索这些值(从队列中)以通过tkinter.IntVar()设置进​​度。 (请注意,如果您使用progbar.step(),则Progressbar会在结尾处重置为0(空),而不是达到4(完全填充)。)

以下是如何快速了解如何将tkinter.IntVar()与进度条结合使用:

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()

以下是基于您自己的示例(重命名为“主要”功能“任意”):

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()

如果你想要的只是向用户表明存在活动的东西 您可以将进度条的mode选项设置为'indeterminate' 指示器在此模式下来回反弹(速度与最大选项有关)。

然后您可以在启动辅助线程之前直接调用Progressbar的start()方法;
然后在stop()返回 False 之后调用secondary_thread.is_alive()

以下是一个例子:

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()

Progressbar reference

答案 2 :(得分:0)

您必须使用: self.pgBar.step(x) 其中“x”是进度条中要增加的数量。 为此,您需要在UI中更新 每self.window.update_idletasks()个陈述之后self.pgBar.step(x)