如何停止Tkinter定时器线程?解决AttributeError报错问题
问题分析与修复方案
核心问题点
Timer类方法与属性命名冲突
Timer类里同时定义了self.stop_threads(Event对象)和stop_threads()方法,实例化后stop_threads会被覆盖为Event对象,调用停止方法时会触发TypeError,因为Event对象不可调用。停止操作未指向正确线程
MainFrame的stop方法里新建了一个Timer实例执行停止,这和正在运行的计时线程完全无关,自然无法停止计时。必须把启动的线程保存为MainFrame的实例属性,才能在停止时引用它。Entry变量绑定错误
计时显示的Entry组件,textvariable参数传了字符串"entry6_var",应该绑定已定义的self.entry1_var,否则变量无法同步更新显示内容。初始值显示问题
Timer类的entry1初始为None,第一次监控线程时会显示空值,建议初始化为"00:00"。
修复后的完整代码
#!/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__() # 重命名Event属性,避免和方法名冲突 self.stop_event = Event() # 初始化默认计时值 self.entry1 = "00:00" self.count = count def run(self): while True: if self.stop_event.is_set(): break mins, secs = divmod(self.count, 60) timeformat = '{:02d}:{:02d}'.format(mins, secs) self.entry1 = timeformat time.sleep(1) self.count += 1 # 修改方法名,避免和属性冲突 def stop(self): self.stop_event.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)) 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)) 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) 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() # 修正textvariable绑定,使用已定义的变量 self.entry1=tk.Entry(self.frame2, textvariable=self.entry1_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): self.button1['state'] = tk.DISABLED # 将线程保存为实例属性,方便stop方法调用 self.timer_thread=Timer(0) self.timer_thread.start() self.monitor(self.timer_thread) def stop(self): # 检查线程是否存在且存活,避免未启动时调用报错 if hasattr(self, 'timer_thread') and self.timer_thread.is_alive(): self.timer_thread.stop() self.text1.insert("end", "stopping\n") def monitor(self,thread): if thread.is_alive(): # 直接更新StringVar,比手动删除插入更高效 self.entry1_var.set(thread.entry1) self.after(100,lambda:self.monitor(thread)) else: self.text1.insert("end", "thread is stopped\n") self.button1['state']=tk.NORMAL class App(tk.Tk): def __init__(self): super().__init__() # 配置主窗口 self.geometry("600x300") self.option_add('*Font', 'Helvetica 9') self.configure(bg="white") self.resizable(False, False) self.title("TEST TIMER") # 配置Frame样式 f = ttk.Style() f.configure('TFrame', background='white') if __name__ == "__main__": app = App() frame = MainFrame(app) app.mainloop()
额外优化点
- 用
self.entry1_var.set()直接更新Entry内容,更符合Tkinter变量绑定的设计逻辑,比手动删除插入更高效。 - 在
stop方法里增加线程存在性检查,避免未启动计时器时点击停止按钮触发错误。 - 给消息显示框的文本插入加上换行符,避免内容堆叠在一起。
内容的提问来源于stack exchange,提问作者bob_the_bob
相关产品推荐
相关产品推荐

