ValSmith
ValSmith

Reputation: 138

Tkinter app does not update timer smoothly

I am pretty new to using Tkinter and I am trying to build an app that displays a timer as one of its features. I am updating a label to display the time from a separate thread. On the display the time does not update smoothly. It freezes for short periods of time and then jumps a second or more. It there a way to keep the updates consistent so the timer is smooth?

It's a simple app so far so it doesn't seem like the CPU or main thread should be busy doing anything else. When you press a button it starts a separate thread that periodically sets the label text. I've tried sleeping between 0-0.1 seconds per update and the result is the same.

window = tk.Tk()
frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()

time_display = tk.Label(master=frame2, text="0.0")
time_display.pack()

update_thread = None


def play_pause():
    global update_thread, stop_loop
    if not update_thread:
        stop_loop = False
        update_thread = threading.Thread(target=update_timer_loop)
        update_thread.start()
    else:
        stop_loop = True
        update_thread = None


stop_loop = False

def update_timer_loop():
    global window
    start = time.time()
    base_time = float(time_display["text"])
    while not stop_loop:
        current_time = time.time() - start + base_time
        window.after(0, lambda: set_text(round(current_time, 2)))
        time.sleep(0.1)


def set_text(text):
    time_display["text"] = text

btn_play = tk.Button(master=frame1, text="Play/Pause", command=play_pause)
btn_play.pack(side=tk.LEFT)

Upvotes: 1

Views: 159

Answers (1)

Novel
Novel

Reputation: 13729

You don't need threading at all in this. I see you tried to use after, and that's the correct approach. The only thing extra to know is that you can use after_cancel to cancel an upcoming event that you scheduled with after. Try this:

import tkinter as tk
import time

window = tk.Tk()

time_display = tk.Label(window, text="0.0")
time_display.pack()

update_thread = None

def play_pause():
    global update_thread, start, base_time
    if update_thread is None:
        start = time.time()
        base_time = float(time_display["text"])
        update_timer_loop() # start the loop
    else:
        time_display.after_cancel(update_thread)
        update_thread = None

def update_timer_loop():
    global update_thread
    current_time = time.time() - start + base_time
    time_display["text"] = round(current_time, 2)
    update_thread = window.after(100, update_timer_loop)

btn_play = tk.Button(master=window, text="Play/Pause", command=play_pause)
btn_play.pack(side=tk.LEFT)

window.mainloop()

Upvotes: 4

Related Questions