2

Let's say we have three tkinter widgets: a label, a treeview and an Optionmenu (will be called 'menu' for short below). I successfully make the menu execute a function once an option is chosen. The brief function looks like this:

def data_process():
    # do something takes time
def print_data()
    data_process() # do something takes time too
    # print stuff to treeview
def refresh_table(self, args):
    label['text'] = 'Executing' # change label text to Executing
    print_data()                # a func. which takes time to run
    label['text'] = 'Done'      # change text to Done
label = tk.Label(parent, text = 'ready')
label.pack()
menu = tk.OptionMenu(parent, var, *list, command = lambda _:refresh_table(self, args))
menu.pack()
table = tk.Treeview(parent)
table.pack()

Function print_data is going to print something to a treeview widget (table). The label widget is like a status bar, telling users what's going on now. So the workflow that I attempt to do is:

  1. Select an option in menu and call refresh_table.

  2. Change label text to 'Executing'.

  3. Execute print_data and print stuff in treeview.

  4. When print_data is done, change the label to 'Done'.

Here is the thing. When I select the option, the program stocks (as expected) to do stuff. However, the label isn't changed to 'Executing' at the beginning. Instead, it's changed to 'Done' when the print_data is done executing (almost simultaneously). I suspect that the command refresh_table effects the target widgets once all demands are done. Because I does see the label flashes out 'Executing' but immediately shows 'Done'. Are there any thoughts about this situation? Any suggestion is appreciated. Thank you!

DerrickTSE
  • 35
  • 5
  • 1
    Try calling `root.update()` after changing the label to 'Executing'. – Kamal Jan 09 '19 at 09:15
  • @Kamal Sir, thank you it works! But can I further ask why? Is it something to do with thread? Just guessing though. Thank you again. – DerrickTSE Jan 09 '19 at 09:33
  • I cannot say for sure without looking at the full code, but I guessed that when `refresh_table` function is called the `root.mainloop()` is not in execution, so the call to `root.update()` does the job. – Kamal Jan 09 '19 at 09:39
  • Turn out that both `.update` and `.update_idletasks` do the job but with different aspects. Thank you again for the fast reply! – DerrickTSE Jan 09 '19 at 10:04
  • @Kamal I just noticed it might come across as if i wanted to adress your comment directly in my answer, in the part where i says that `update_idletasks` is, in my opinion, better then `update`, but i want to make it clear that that was not my intention - i had not read your comment when writing my answer. Hope i did not offend you :-) – Flob Jan 09 '19 at 13:02
  • @Flob, Don't worry, none taken. Actually, I did not know about the use of `update_idletasks`, so your answer actually taught me something new, thanks for that. :-) – Kamal Jan 10 '19 at 01:13

1 Answers1

5

I suspect that the command refresh_table effects the target widgets once all demands are done.

That's true. Your GUI will update every time the mainloop is entered. While your function refresh_table is running, no events (like updating your label) will be processed, since those only occur during idle times (hence their other name, "idle tasks"). But because the UI isn't idle during the execution of refresh_table, the event loop is not accessible until all code in the function is done (because your whole program runs on the same thread) and the redrawing event is pending. You can fix this by calling update_idletasks, which will process all pending events immediately. Would be done like this (assuming that your main window is called parent and you are inside a class definition, the important thing is that you call update_idletasks on your main window directly after changin the label's text):

def refresh_table(self, args):
    label['text'] = 'Executing'
    self.parent.update_idletasks()
    print_data()
    label['text'] = 'Done'

There's also update, which will not only call pending idle tasks, but rather process basically everything that there is to process, which is unnecessary in your case. A very nice explanation why update_idletasks is mostly preferable to update can be found in the answer to this question.

Flob
  • 898
  • 1
  • 5
  • 14