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.
