I have a time consuming task that I'd like to show in UI the progress of, or at least just tell the user the program's not frozen. I'd like to show this in two ways: through Label and ProgressBar. For a more specific context, I'm using Python 3.6.7 for this.
I am aware of the indeterminate and determinate modes of the Progressbar, and the difference in implementation of the two, but my main problem is how I can make the Progressbar appear and not freeze before the time consuming function is run.
Same goes with the Label. I'm okay with some text not shown or skipped if the operation is fast enough, but some text should be left to remind the user what's currently happening.
I have tried the solutions in these answers, both the threaded and non-threaded ones.
Originally, my code is similar to this:
import threading
import time # to simulate work
from tkinter import *
from tkinter import filedialog
from tkinter.ttk import Progressbar
class DemoWindow(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.status = StringVar()
        self.status.set("Click when ready")
        self.label = Label(text="Click when ready")
        self.label.grid(row=0)
        self.button = Button(text="Click me", command=self.run_foo)
        self.button.grid(row=1)
        self.indeterminate_bar = Progressbar(orient='horizontal', mode='indeterminate')
        self.determinate_bar = Progressbar(orient='horizontal', mode='determinate')
    def run_foo(self):
        # to simulate asking user for something
        self.label.configure(text="Where's the sauce?")
        foo_dir = filedialog.askdirectory()
        if foo_dir is not None:
            # start the progress bars
            # it should be moving at this point
            self.show_progress()
            self.start_progress()
            self.label.configure(text="Please wait...")
            self.time_consuming_task()
            # stop or freeze progress bar while
            # asking the user about something again
            self.stop_progress()
            # ask something again...
            self.label.configure(text="Where's the meat?")
            bar_dir = filedialog.askdirectory()
            if bar_dir is not None:
                # start it again
                self.start_progress()
                self.label.configure(text="Please wait...")
                self.another_time_consuming_task()
        # stop and hide progress bars
        self.hide_progress()
        self.label.configure(text="Done!")
    def show_progress(self):
        self.indeterminate_bar.grid(row=2)
        self.determinate_bar.grid(row=3)
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def start_progress(self):
        self.indeterminate_bar.start(50)
        self.determinate_bar.start(50)
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def stop_progress(self):
        self.indeterminate_bar.stop()
        self.determinate_bar.stop()
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def hide_progress(self):
        self.indeterminate_bar.stop()
        self.determinate_bar.stop()
        self.indeterminate_bar.grid_forget()
        self.determinate_bar.grid_forget()
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def time_consuming_task(self):
        time.sleep(5)
    def another_time_consuming_task(self):
        time.sleep(3)
if __name__ == '__main__':
    root = Tk()
    root.title("The UI Dilemma")
    root.geometry("400x100")
    root.resizable(0,0)
    DemoWindow(root).grid()
    root.mainloop()
Here's my attempt at the threaded approach, inspired by the linked questions' answers:
import threading
import time # to simulate work
from tkinter import *
from tkinter import filedialog
from tkinter.ttk import Progressbar
class DemoWindow(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.status = StringVar()
        self.status.set("Click when ready")
        self.label = Label(text="Click when ready")
        self.label.grid(row=0)
        self.button = Button(text="Click me", command=self.start_helper_thread)
        self.button.grid(row=1)
        self.indeterminate_bar = Progressbar(orient='horizontal', mode='indeterminate')
        self.determinate_bar = Progressbar(orient='horizontal', mode='determinate')
    def run_foo(self):
        # to simulate asking user for something
        self.label.configure(text="Where's the sauce?")
        foo_dir = filedialog.askdirectory()
        if foo_dir != "":
            # start the progress bars
            # it should be moving at this point
            self.show_progress()
            self.start_progress()
            self.label.configure(text="Please wait...")
            self.time_consuming_task()
            # stop or freeze progress bar while
            # asking the user about something again
            self.stop_progress()
            # ask something again...
            self.label.configure(text="Where's the meat?")
            bar_dir = filedialog.askdirectory()
            if bar_dir != "":
                # start it again
                self.start_progress()
                self.label.configure(text="Please wait...")
                self.another_time_consuming_task()
        # stop and hide progress bars
        self.hide_progress()
        self.label.configure(text="Done!")
    def start_helper_thread(self):
        global helper_thread
        helper_thread = threading.Thread(target=self.run_foo())
        helper_thread.daemon = True
        helper_thread.start()
        self.after(500, self.check_helper_thread)
    def check_helper_thread(self):
        if helper_thread.is_alive():
            self.after(500, self.check_helper_thread())
        else:
            self.hide_progress()
    def show_progress(self):
        self.indeterminate_bar.grid(row=2)
        self.determinate_bar.grid(row=3)
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def start_progress(self):
        self.indeterminate_bar.start(50)
        self.determinate_bar.start(50)
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def stop_progress(self):
        self.indeterminate_bar.stop()
        self.determinate_bar.stop()
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def hide_progress(self):
        self.indeterminate_bar.stop()
        self.determinate_bar.stop()
        self.indeterminate_bar.grid_forget()
        self.determinate_bar.grid_forget()
        self.indeterminate_bar.update_idletasks()
        self.determinate_bar.update_idletasks()
    def time_consuming_task(self):
        time.sleep(5)
    def another_time_consuming_task(self):
        time.sleep(3)
if __name__ == '__main__':
    root = Tk()
    root.title("The UI Dilemma")
    root.geometry("400x100")
    root.resizable(0,0)
    DemoWindow(root).grid()
    root.mainloop()
Basically the same as the former, but I added self.start_helper_thread() to start the thread that will run the self.run_foo() function, changed the command in self.button to call self.start_helper_thread instead, and added self.check_helper_thread() that supposedly checks if the helper_thread is still alive every 500 ms.  
The former code block is quite similar already to the non-threaded solutions in the linked questions, right? I expected that update_idletasks() would do the trick for me. By that I mean:
- After the first self.label.configure(text="Please wait...")line,self.indeterminate_barandself.determinate_barshould be moving whileself.time_consuming_task()is running. TheLabel's text that is displayed should be"Please wait..."before and afterself.time_consuming_task().
- Then, the two ProgressBars willstop()andself.label's text would be"Where's the meat?"by the time the secondfiledialog.askdirectory()runs.
- If bar_diris valid, theProgressBars will start again, and theself.label's text would be"Please wait..."again.
- self.another_time_consuming_task()will execute with the- ProgressBars moving and the- Label's text as- Please wait..."
- After self.another_time_consuming_task()ends, theProgressBars will be hidden and theLabel's text will be left at"Done!"
I expected the same too of the thread method. Actually I expected this behavior right off the bat before all of this- I coded them in that order, so why doesn't it execute in that order?
