sadalsuud
sadalsuud

Reputation: 479

bar progress in tkinter with thread

hello people I have a problem, with a progress bar in tkinter, you will see:

I execute a method and I want that while this action is being performed, a progress bar appears, the problem is that the bar does not stop when the method ends, I don't know how to do that ... some help please

import threading
import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Progressbar example")
        self.button = tk.Button(self, text="Start", command=self.start_action)
        self.button.pack(padx=10, pady=10)

    def start_action(self):
        self.button.config(state=tk.DISABLED)
        t = threading.Thread(target=self.contar)
        windows_bar = WinProgressBar(self)
        self.after(t.start())

    def contar(self):
        for i in range(1000000):
            print("está contando", i)
        return True


class WinProgressBar(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Barr progress")
        self.geometry("300x200")
        self.progressbar = ttk.Progressbar(self, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.start(20)


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

Upvotes: 1

Views: 3638

Answers (1)

furas
furas

Reputation: 142651

You can try to stop it in threat

def contar(self):
    for i in range(500):
        print("está contando", i)
    
    self.windows_bar.progressbar.stop()
    self.windows_bar.destroy()
    self.button.config(state=tk.NORMAL)

import threading
import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Progressbar example")
        self.button = tk.Button(self, text="Start", command=self.start_action)
        self.button.pack(padx=10, pady=10)

    def start_action(self):
        self.button.config(state=tk.DISABLED)
        
        t = threading.Thread(target=self.contar)
        t.start()

        self.windows_bar = WinProgressBar(self)
        
    def contar(self):
        for i in range(500):
            print("está contando", i)

        print('stop')            
        self.windows_bar.progressbar.stop()
        self.windows_bar.destroy()
        self.button.config(state=tk.NORMAL)
        
        
class WinProgressBar(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Barr progress")
        self.geometry("300x200")
        self.progressbar = ttk.Progressbar(self, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.start(20)


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

Many GUI frameworks don't like to change widgets in thread and then you can use variable self.running (which will be shared between both threads) to inform program if thread is running.

def contar(self):
    self.running = True

    for i in range(500):
        print("está contando", i)

    self.running = False

and in main thread you can use after() to periodically check this variable to stop progressbar and/or close window - and this time main thread is changing widgets.

def check_thread(self):
    if self.running:
        self.after(1000, self.check_thread) # run again after 1s (1000ms)
    else:
        print('stop')
        self.windows_bar.progressbar.stop()
        self.windows_bar.destroy()
        self.button.config(state=tk.NORMAL)

import threading
import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Progressbar example")
        self.button = tk.Button(self, text="Start", command=self.start_action)
        self.button.pack(padx=10, pady=10)

    def start_action(self):
        self.button.config(state=tk.DISABLED)
        
        t = threading.Thread(target=self.contar)
        t.start()

        self.windows_bar = WinProgressBar(self)

        self.check_thread() # run first time
        
    def contar(self):
        self.running = True
        for i in range(500):
            print("está contando", i)
        self.running = False

    def check_thread(self):
        if self.running:
            self.after(1000, self.check_thread) # run again after 1s (1000ms)
        else:
            print('stop')
            self.windows_bar.progressbar.stop()
            self.windows_bar.destroy()
            self.button.config(state=tk.NORMAL)
        
        
class WinProgressBar(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Barr progress")
        self.geometry("300x200")
        self.progressbar = ttk.Progressbar(self, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.start(20)


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

If threads can't share variables then you can use queue to send information from thread to main thread.

import threading
import queue
import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Progressbar example")
        self.button = tk.Button(self, text="Start", command=self.start_action)
        self.button.pack(padx=10, pady=10)

    def start_action(self):
        self.button.config(state=tk.DISABLED)
        
        self.q = queue.Queue()
        t = threading.Thread(target=self.contar, args=(self.q,))
        t.start()

        self.windows_bar = WinProgressBar(self)
        
        self.check_queue()
        
    def contar(self, q):
        for i in range(200):
            print("está contando", i)
        
        q.put('finished')
        
    def check_queue(self):
        if self.q.empty() or self.q.get() != 'finished':
            self.after(1000, self.check_queue)
        else:
            print('stop')
            self.windows_bar.progressbar.stop()
            self.windows_bar.destroy()
            self.button.config(state=tk.NORMAL)
        
        
class WinProgressBar(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Barr progress")
        self.geometry("300x200")
        self.progressbar = ttk.Progressbar(self, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.start(20)


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

Upvotes: 2

Related Questions