Home > Software engineering >  How to Stop Tkinter TImer Thread?
How to Stop Tkinter TImer Thread?

Time:12-09

I am trying to make a simple count-up timer in tkinter. The timer starts and counts up but I can't figure out how to stop it. I am trying to use threading.event but I'm missing something and I just can't get the code right.

Running the code below gives me "attributeError: 'MainFrame' object has no attribute 'x'".

#!/usr/bin/python3.9
   
import tkinter as tk
from tkinter import ttk
from threading import Thread
from threading import Event
import time

class Timer(Thread):
    def __init__(self, count):
        super().__init__()

        self.stop_threads = Event()
        self.entry1 = None
        self.count = count

    def run(self):
        while True:
            if self.stop_threads.is_set():
                break;
            else:
                mins, secs = divmod(self.count, 60)
                timeformat = '{:02d}:{:02d}'.format(mins, secs)
                #print(timeformat, end='\r')
            
                self.entry1 = timeformat
                time.sleep(1)
                self.count  = 1 

    def stop_threads(self):
        self.stop_threads.set()

class MainFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)
     
        ### TOP BUTTON ROW
        self.frame1=ttk.Frame(self, padding = (0, 50, 0, 30), ) ## LH padding, TOP padding, RH padding, BOTTOM padding
        self.frame1.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        ### TOP TEXT BOX
        self.frame2=ttk.Frame(self, padding = (0, 0, 0, 0), ) ## LH padding, TOP padding, RH padding, BOTTOM padding
        self.frame2.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        ### MID TEXT BOX
        self.frame3=ttk.Frame(self, padding = (40, 20, 40, 0))
        self.frame3.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        # show the frame on the container
        self.pack()

        ### START BUTTON
        self.button1=tk.Button(self.frame1, text='START')
        self.button1['command'] = self.delay
        self.button1.pack(expand=True, padx=0, pady=0, ipadx=0, ipady=0, side="left")

        ### STOP BUTTON
        self.button2=tk.Button(self.frame1, text='STOP')
        self.button2['command'] = self.stop
        self.button2.pack(expand=True, padx=0, pady=0, ipadx=0, ipady=0, side="right")

        ### TIMER DISPLAY LABEL
        self.entry1_label=tk.Label(self.frame2 , text='TIMER', bg="white", fg="blue")
        self.entry1_label.pack(padx=10, pady=0, ipadx=0, ipady=0, expand=False, side="left")

        ### TIMER DISPLAY
        self.entry1_var = tk.StringVar()
        self.entry1=tk.Entry(self.frame2, textvariable="entry6_var", width=10, justify="center")
        self.entry1.pack(padx=0, pady=0, ipadx=0, ipady=4, expand=False, side="left")

        ### MSG DISPLAY
        self.text1 = tk.Text(self.frame3, height=1)
        self.text1.pack(expand=False, padx=0, pady=0, ipadx=0, ipady=0, side="left")        

    def delay(self):
        event = Event()
        self.button1['state'] = tk.DISABLED
        timer_thread=Timer(0)
        timer_thread.start()
        self.monitor(timer_thread)  
 
    def stop(self):
        x = Timer(0)
        x.stop_threads()
        self.text1.insert("end", "stopping")

    def monitor(self,thread):
        if thread.is_alive():
            self.entry1.delete('0', tk.END)
            self.entry1.insert("end", thread.entry1)
            #check the thread every 100ms
            self.after(100,lambda:self.monitor(thread))
        else:
            self.text1.insert("end", "thread is stopped")
            self.button1['state']=tk.NORMAL

class App(tk.Tk):
    def __init__(self):
        super().__init__()  

        # configure the root window
        self.geometry("600x300")
        self.option_add('*Font', 'Helvetica 9') ## drop down menu font & size / https://coderslegacy.com/python/problem-solving/change-font-in-tkinter/
        self.configure(bg="white") ## bottom background colour
        self.resizable(False, False)

        self.title("TEST TIMER")

        ### frame style 
        f = ttk.Style()
        f.configure('TFrame', background='white') ## background colour

    #def exit(self):
        #self.destroy() 

if __name__ == "__main__":
    app = App()
    frame = MainFrame(app)
    app.mainloop()

CodePudding user response:

Remove

def stop_threads(self):
    self.stop_threads.set()

from Timer and try x.stop_threads.set() instead of x.stop_threads()

Part of the issue is that your Timer.__init__() says self.stop_threads = Event() but def stop_threads(self) is overriding that because it has the same name. Ultimately, since you're instantiating the Event class with self.stop_threads, you don't need the method anyway.

You might run into some trouble, however, because both your MainFrame.delay and MainFrame.stop methods are instantiating Timer, and those timers will have separate stop Events.

You can also fix up the run method a bit:

def run(self):
    while not self.stop_threads.is_set():
        mins, secs = divmod(self.count, 60)
        timeformat = '{:02d}:{:02d}'.format(mins, secs)
        # print(timeformat, end='\r')
        self.entry1 = timeformat
        time.sleep(1)
        self.count  = 1 

All that aside, I'd be careful using Thread and sleep alongside tkinter. If all you want is a countdown timer, you're better off using tkinter.after since it won't cause your app to hang.

  • Related