Home > database >  How to use Tkinter .after() method to delay a loop instead time.sleep()?
How to use Tkinter .after() method to delay a loop instead time.sleep()?

Time:10-27

I´m trying to create a simple thing: a loop with a delay of x seconds between iterations, triggered by a Tkinter button command.

The obvious answer is to use time.sleep(), however, this actually freezes the mainloop process, avoiding other events to be captured.

I´ve searched and the recommendation is to use the tkinter.after() method, however, I still can't make the loop take time between iterations.

Any help? Simplified code is below.

import tkinter as tk
import tkinter.scrolledtext as st
import time 

# function to be activated by button

def do_some_action():
    for i in range(10):

        # just write some variable text to check if it is working
        txt_bigtextlog.insert(tk.END,'Row text {} off 10\n'.format(i))

        # tk.END to point the scrolling text to latest line
        txt_bigtextlog.see(tk.END)

        # I´ve tried w/o success (1000 is miliseconds):
        # mywindowapp.after(1000)
        # btn_action.after(1000)
        
        time.sleep(1)
        mywindowapp.update()
    return()

# Create the application main window
mywindowapp = tk.Tk()

# create some label, just to visualize something
lbl_justsomelabel = tk.Label(text='Just some label here')
lbl_justsomelabel.grid(row=0,column=0,sticky='NSEW',padx=10,pady=10)

# create a button, just so simulate loop triggering
btn_action = tk.Button(text='Start process',command=do_some_action)
btn_action.grid(row=1,column=0,sticky='NSEW',padx=10,pady=10)

# create a scrolling text just to do some example iterable action
txt_bigtextlog = st.ScrolledText(mywindowapp,width = 30,height = 8)
txt_bigtextlog.grid(row=2,column = 0, columnspan=3,sticky='NSEW', pady = 10, padx = 10)
txt_bigtextlog.insert(tk.INSERT,'')

mywindowapp.mainloop()

CodePudding user response:

The basic concept is to write a function that does one iteration of the loop, and then schedules itself to run again in the future if some condition hasn't been met. To facilitate this you can initialize the loop counter as a keyword argument, and you can increase the value each time the function schedules itself to run again.

def do_some_action(i=0):
    # just write some variable text to check if it is working
    txt_bigtextlog.insert(tk.END,'Row text {} off 10\n'.format(i))

    # tk.END to point the scrolling text to latest line
    txt_bigtextlog.see(tk.END)

    if i 1 < 10:
        txt_bigtextlog.after(1000, do_some_action, i 1)

When you click the button, do_some_action is called without a parameter. That causes i to be initialized to zero. It performs one iteration of the loop and then schedules itself to be called again if the condition hasn't been met. Each time it schedules itself it will add one to i.

CodePudding user response:

I would suggest you to use the tksleep for this task. While @Bryan Oakley's answer is the canonical and produces lesser overhead, tksleep can ease things out by a lot. You can have a control flow over multiple for-loops or even while loops are possible with this technique. Take this example text here:

example = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Elit ut aliquam purus sit amet luctus venenatis.
Viverra tellus in hac habitasse platea dictumst vestibulum.
Dignissim suspendisse in est ante in nibh mauris.
Rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar.
Lorem ipsum dolor sit amet consectetur adipiscing elit ut aliquam.
Volutpat maecenas volutpat blandit aliquam etiam erat.
At urna condimentum mattis pellentesque id nibh tortor.
Eu ultrices vitae auctor eu augue ut lectus arcu.
Et odio pellentesque diam volutpat commodo sed egestas.
Nam libero justo laoreet sit amet cursus sit.
Arcu bibendum at varius vel.

Aliquam id diam maecenas ultricies mi eget mauris pharetra et.
Eget nullam non nisi est sit.
Volutpat ac tincidunt vitae semper quis lectus.
Enim sit amet venenatis urna. Vitae proin sagittis nisl rhoncus mattis.
Justo eget magna fermentum iaculis eu non diam phasellus vestibulum.
Scelerisque in dictum non consectetur a erat nam.
Neque vitae tempus quam pellentesque nec nam aliquam sem et.

Nunc faucibus a pellentesque sit amet porttitor.
A scelerisque purus semper eget duis.
Proin libero nunc consequat interdum varius sit.
Nunc sed velit dignissim sodales ut eu sem integer vitae.
Elementum curabitur vitae nunc sed velit.
Vulputate odio ut enim blandit volutpat maecenas volutpat.
Etiam dignissim diam quis enim lobortis scelerisque fermentum dui faucibus.
Eu nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper.
Varius duis at consectetur lorem donec.
Suspendisse potenti nullam ac tortor vitae.
Nisi porta lorem mollis aliquam ut porttitor leo a.
Vitae sapien pellentesque habitant morbi tristique senectus et.
Porttitor massa id neque aliquam vestibulum.
Dui accumsan sit amet nulla facilisi morbi tempus iaculis urna.
'''

and look how easy it is, to write an animation of writing with just a few lines of code. Of course it's still based on .after but think about the workarounds you would need to write code that behaves exactly the same as the following with only after and you will notice, that this technique has some serious benefits.

import tkinter as tk

def tksleep(t):
    'emulating time.sleep(seconds)'
    ms = int(t*1000)
    root = tk._get_default_root()
    var = tk.IntVar(root)
    root.after(ms, lambda: var.set(1))
    root.wait_variable(var)

def animated_write(line):
    for char in line:
        textbox.insert(tk.END, char)
        tksleep(0.1)
    textbox.insert(tk.END, '\n')

def read_text(text):
    for line in text.splitlines():
        animated_write(line)

    
root = tk.Tk()
textbox = tk.Text(root)
textbox.pack()
read_text(example)
root.mainloop()
  • Related