How to connect a progress bar to a function?

后端 未结 3 1313
心在旅途
心在旅途 2020-11-29 01:56

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


        
相关标签:
3条回答
  • 2020-11-29 02:11

    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

    0 讨论(0)
  • 2020-11-29 02:14

    To understand the 'freezing' you need to understand mainloop(). Calling this method starts the tkinter event 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 Thread to run your function. It's recommended that secondary threads are not given access to tkinter objects. Allen B.Taylor, author of mtTkinter, states:

    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".

    You can have the secondary thread put information into a Queue. Then have a function that checks the Queue every x milliseconds, within the mainloop, via the after() method.

    First, decide what you want the value of the Progressbar's maximum option 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=4 and 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).)

    Here's a quick look at how you can use a tkinter.IntVar() with a 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"):

    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 mode option to 'indeterminate'.
    The indicator bounces back and forth in this mode (the speed relates to the maximum option).

    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.

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

    → Progressbar reference

    0 讨论(0)
  • 2020-11-29 02:24

    Since tkinter is single threaded, you need another thread to execute your main function 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.

    The following code is based on a full detailed example on 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 example on ActiveState is a bit messy IMO (the ThreadedClient is 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.

    0 讨论(0)
提交回复
热议问题