Python Tkinter番茄钟重复点Start按钮倒计时加速问题咨询
Python Tkinter番茄钟重复点击Start按钮倒计时加速问题
问题背景
使用Python基于Tkinter实现番茄钟程序,解决基础运行问题后发现异常:重复点击Start按钮会导致倒计时速度变快,点击次数越多速度越快。
注:提供的代码存在制表符与空格混用的格式问题。
from tkinter import * from time import * from threading import Thread # ---------------------------- CONSTANTS ------------------------------- # PINK = "#e2979c" RED = "#e7305b" GREEN = "#9bdeac" YELLOW = "#f7f5dd" FONT_NAME = "Courier" WORK_MIN = 25 SHORT_BREAK_MIN = 5*60000 LONG_BREAK_MIN = 20*60000 class Reset: def __init__(self): self.click = False self.activation = False def button_command(self): self.click = True def timer_command(self): return self.click # ---------------------------- TIMER RESET ------------------------------- # timer_reset = Reset() reset = timer_reset.click # ---------------------------- TIMER MECHANISM ------------------------------- # def timer_mechanism(): minute=0 if counter[0]<60000: if counter[0] % 1000 == 0 and counter[0]/1000<10: canvas.itemconfig(clock_counter, text=(f"00:0{int(counter[0]/1000)}")) canvas.update_idletasks() elif counter[0]% 1000==0 and counter[0]/1000>=10: canvas.itemconfig(clock_counter, text=(f"00:{int(counter[0] / 1000)}")) canvas.update_idletasks() elif counter[0]>=60000: if counter[0]%1000==0: transition=int(counter[0]/1000) while transition>=60: minute+=1 transition-=60 second=transition if second<10 and minute<10: canvas.itemconfig(clock_counter,text=(f"0{minute}:0{second}")) canvas.update_idletasks() elif second>=10 and minute<10: canvas.itemconfig(clock_counter,text=(f"0{minute}:{second}")) canvas.update_idletasks() elif minute>=10 and second<10: canvas.itemconfig(clock_counter, text=(f"{minute}:0{second}")) canvas.update_idletasks() else: canvas.itemconfig(clock_counter,text=(f"{minute}:{second}")) canvas.update_idletasks() # -----------------------------New countdown mechanism----------------------------# i = [4] def start_countdown(): if not timer_reset.click and i[0] > 0: # keep listening to the function that receives from the button while doing an after for the 25 min and 10 min respectively.3 timer.config(text="Work", fg=GREEN) window.update_idletasks() # the solution to this problem is to break up the after process instead of doing it all at the same time make use of small increments so that this after could finish withing a second or two and then go to another function that contains all the timer_reset.click values update and come everytime until we reach a limit somewhere probably an array value that will be updated until we get to the 25 minute mark so this insures as the signal from the button will be accepted. execute("Work") # start=time() # end=time() # while end-start<5: # end=time() # timer_reset.click=timer_reset.timer_command() else: window.destroy() timer_reset.click = False i[0] = 4 beginning() def rest(): global start_time start_time=0 timer_reset.activation = True if not timer_reset.click: global restloop restloop = True global workloop workloop = False timer.config(text="Break", fg=PINK) if i[0] == 4: global frame frame = Frame(window, width=20, height=20) frame.grid(row=3, column=1) tick = Label(frame, text="✔", font=(FONT_NAME, 12, "bold"), bg=YELLOW, fg=GREEN) tick.pack(side=LEFT) window.update_idletasks() if i[0] > 0: execute("Break") if timer_reset.click: timer_reset.click = False window.destroy() beginning() else: window.destroy() beginning() else: window.destroy() i[0] = 4 timer_reset.click = False beginning() i[0] -= 1 counter=[0] def execute(identifier): if identifier=="Work": window.after(1,mirror,LONG_BREAK_MIN) if identifier=="Break": window.after(1,mirror,SHORT_BREAK_MIN) def mirror(value): if timer_reset.click: window.destroy() i[0]=4 timer_reset.click=False counter[0]=0 beginning() elif counter[0]<value: counter[0]+=1 timer_mechanism() if value==LONG_BREAK_MIN: execute("Work") if value==SHORT_BREAK_MIN: execute("Break") elif value==LONG_BREAK_MIN: counter[0]=0 window.deiconify() window.attributes("-topmost",1) window.after(300,window.attributes,"-topmost",0) window.update() rest() elif value==SHORT_BREAK_MIN: counter[0]=0 window.deiconify() window.attributes("-topmost",1) window.after(300,window.attributes,"-topmost",0) window.update() start_countdown() # ---------------------------- UI SETUP ------------------------------- # def beginning(): global window window = Tk(className="Pomodoro") window.title("Pomodoro") window.config(pady=50, padx=100, bg=YELLOW) window.wm_state("normal") global timer timer = Label(text="Timer", font=(FONT_NAME, 50, "bold"), bg=YELLOW, foreground=GREEN) timer.grid(row=0, column=1) global canvas canvas = Canvas(width=200, height=223, bg=YELLOW, highlightthickness=0) tomato = PhotoImage(file="tomato.png") canvas.create_image(100, 100, image=tomato) global clock_counter clock_counter=canvas.create_text(100, 120, text="00:00", fill="white", font=(FONT_NAME, 35, "bold")) canvas.grid(row=1, column=1) start = Button(text="Start", highlightthickness=0, borderwidth=0, command=start_countdown) start.grid(row=2, column=0) end = Button(text="Reset", highlightthickness=0, borderwidth=0, command=timer_reset.button_command) end.grid(row=2, column=2) window.mainloop() beginning()
程序UI效果

异常表现
程序启动后倒计时正常运行时,若用户再次点击Start按钮,倒计时运行速度会变快;如果持续点击Start按钮且不点击Reset按钮,倒计时速度会越来越快。
问题根因
猜想完全正确,故障核心就是after方法的调用逻辑缺陷:
- Tkinter的
after()方法不会自动覆盖或取消之前已经提交到事件队列的定时任务,每调用一次就会新增一条独立的定时执行链路。 - 每次点击Start按钮触发
start_countdown()时,都会调用execute("Work"),新增一条after(1, mirror, LONG_BREAK_MIN)的定时任务。 - 每条独立链路都会单独执行
counter[0] += 1的计数操作,相当于每1ms有N个任务同时给计数器累加,点击Start次数越多,并行的计数链路越多,计数器增长越快,倒计时就会呈现加速效果。 - 额外逻辑缺陷:当前代码把1ms作为1个计数单位,时间换算逻辑混乱,本身计时精度就不符合预期。
修复方案
- 新增全局变量存储
after返回的任务ID,每次启动新的定时任务前,先调用window.after_cancel(任务ID)取消之前未执行完的定时链路,避免多链路同时计数。 - 统一时间单位:将
after的触发间隔改为1000ms(1秒),计数器每次加1对应1秒,工作、休息时长统一换算为秒单位计算,修正时间换算逻辑。 - 增加运行状态判断:倒计时运行过程中,要么禁用Start按钮,要么检测到已有计时任务运行时直接忽略Start的点击事件,从入口避免重复启动。
- 统一代码缩进,全部使用4个空格代替制表符,避免缩进引发的逻辑错误。
内容的提问来源于stack exchange,提问作者How why e
相关产品推荐
相关产品推荐

