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 Event
s.
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.