Update Tkinter GUI while in outside loop

Viewed 189

I have my code set up to print out to console it's progress as it loops through a few thousand items. This works perfectly well until I go to package it up and distribute it to others as there is no longer a console. My current method works, but the GUI consistently hangs after a few iterations while the loop continues to run.

The example code below runs, but my loops are much more complicated and hang up. I'm curious if there is a better way to accomplish this as I love using these GUI's but I have yet been able to find a good way of sending data to the GUI while I'm in a different loop.

from tkinter import *
import time

def do_loop(num_cycles):
    for i in range(0, int(num_cycles)):
        time.sleep(.25)
        # print(i)
        GUI_MSG.set(str(i))
        Tk.update_idletasks(FORM)

FORM = Tk()
GUI_MSG = StringVar()
FORM.wm_title('Perform Element Analysis')

IO_FRAME = LabelFrame(FORM, text=' Input/Output Directories ')
IO_FRAME.grid(row=0, sticky='W', padx=5, pady=5, ipadx=5, ipady=5)

Label(IO_FRAME, text="Numer of Loops").grid(
    row=1, column=1, padx=5, pady=(10, 2), sticky='E')
NUMBER_LOOPS = Entry(IO_FRAME, width=10)
NUMBER_LOOPS.grid(row=1, column=2, columnspan=4)

Button(FORM, text='Generate Tables', command=lambda: do_loop(
    NUMBER_LOOPS.get())).grid(row=3, column=0, sticky='WE', padx=5, pady=5)

Label(FORM, textvariable=GUI_MSG).grid(row=2, sticky='WE', padx=5, pady=20)

FORM.mainloop()
2 Answers
time.sleep()

Sleep freezes the tkinter GUI, such that you cannot carry out any other action out on the GUI while it is running. As is stated in the python doc(https://docs.python.org/3/library/time.html#time.sleep)

Suspend execution of the calling thread for the given number of seconds

You will be better served, considering your desired output to use

.after()

And you really might not need update_idletasks(). See if this works for you:

 from tkinter import *

class myFormWindow():
   def __init__(self,FORM):
      self.count=0
      self.GUI_MSG = StringVar()
      self.FORM=FORM
      self.FORM.wm_title('Perform Element Analysis')

      IO_FRAME = LabelFrame(self.FORM, text=' Input/Output Directories ')
      IO_FRAME.grid(row=0, sticky='W', padx=5, pady=5, ipadx=5, ipady=5)

      Label(IO_FRAME, text="Numer of Loops").grid(
      row=1, column=1, padx=5, pady=(10, 2), sticky='E')
      self.NUMBER_LOOPS = Entry(IO_FRAME, width=10)
      self.NUMBER_LOOPS.grid(row=1, column=2, columnspan=4)
      Button(self.FORM, text='Generate Tables', command=self.get_cycles).grid(row=3, column=0, sticky='WE', padx=5, pady=5)
      Label(self.FORM, textvariable=self.GUI_MSG).grid(row=2, sticky='WE', padx=5, pady=20)



   def get_cycles(self):
      self.num_cycles=self.NUMBER_LOOPS.get()
      self.do_loop()

   def do_loop(self):
       if self.count<int(self.num_cycles):
       self.GUI_MSG.set(str(self.count))
       self.count+=1
       self.FORM.after(1000,self.do_loop)


if __name__=="__main__":
   FORM=Tk()
   form_window=myFormWindow(FORM)
   FORM.mainloop()

Following the initial logic of your question and using .after() to reference outside of a function:

from tkinter import *

def do_loop():
     global count,num_cycles,GUI_MSG
     if count<int(num_cycles):
         GUI_MSG.set(str(count))
         count+=1
         FORM.after(1000,do_loop)

def get_cycles():
     global num_cycles
     num_cycles=NUMBER_LOOPS.get()
     do_loop()

count=0    
FORM = Tk()
GUI_MSG = StringVar()
FORM.wm_title('Perform Element Analysis')

IO_FRAME = LabelFrame(FORM, text=' Input/Output Directories ')
IO_FRAME.grid(row=0, sticky='W', padx=5, pady=5, ipadx=5, ipady=5)

Label(IO_FRAME, text="Numer of Loops").grid(
row=1, column=1, padx=5, pady=(10, 2), sticky='E')
NUMBER_LOOPS = Entry(IO_FRAME, width=10)
NUMBER_LOOPS.grid(row=1, column=2, columnspan=4)

Button(FORM, text='Generate Tables', command=get_cycles).grid(row=3, column=0, sticky='WE', padx=5, pady=5)

Label(FORM, textvariable=GUI_MSG).grid(row=2, sticky='WE', padx=5, pady=20)

FORM.mainloop()
Related