如何从Tkinter根窗口创建多个独立运行的倒计时子窗口?
问题分析与解决
你的代码核心问题是用while循环配合time.sleep(1)实现倒计时——Tkinter是单线程架构,主事件循环负责处理所有UI更新、用户输入,一旦这个循环被time.sleep或长时间while卡住,整个程序的响应都会停滞,导致旧窗口暂停、菜单点击无反应,只有最后启动的倒计时能抢占阻塞的循环。
正确的做法是用Tkinter自带的after()方法实现定时任务,它不会阻塞主循环,而是把任务放入主循环的事件队列,到时间再执行,这样多个窗口的倒计时就能独立运行。
以下是修改后的完整代码:
import time import tkinter as tk from tkinter import messagebox def countdown(win): child = tk.Toplevel(win) child.geometry('400x300') child.resizable(0, 0) def _show_current_clock_time(label): str_clock_time = time.strftime('%H:%M:%S %p') label.config(text=str_clock_time) label.after(1000, lambda: _show_current_clock_time(label)) def _countdown_step(child, v_hrs, v_mins, v_secs, total_seconds): if total_seconds < 0: messagebox.showinfo('Times Up!') v_secs.set('00') v_mins.set('00') v_hrs.set('00') return hour = total_seconds // 3600 minute = (total_seconds % 3600) // 60 second = total_seconds % 60 # 格式化数字为两位字符串,避免显示单个数字 v_secs.set(f"{second:02d}") v_mins.set(f"{minute:02d}") v_hrs.set(f"{hour:02d}") # 1秒后调度下一次倒计时步骤 child.after(1000, _countdown_step, child, v_hrs, v_mins, v_secs, total_seconds - 1) def start_countdown(child, v_hrs, v_mins, v_secs, int_secs): if int_secs is not None: total_seconds = int_secs else: try: total_seconds = int(v_hrs.get()) * 3600 + int(v_mins.get()) * 60 + int(v_secs.get()) except ValueError: messagebox.showerror("错误", "请输入有效的数字时间") return # 启动倒计时流程 _countdown_step(child, v_hrs, v_mins, v_secs, total_seconds) tk.Label(child,text='Countdown Clock and Timer').pack() tk.Label(child, font='arial 15 bold', text='current time :').place(x=40, y=70) # 当前时间显示标签 l_curr_time = tk.Label(child) l_curr_time.place(x=190, y=70) _show_current_clock_time(l_curr_time) # 时间输入变量与控件 v_secs = tk.StringVar(value='00') e_secs = tk.Entry(child, textvariable=v_secs, width=2, font='arial 12') e_secs.place(x=250, y=155) v_mins = tk.StringVar(value='00') e_mins = tk.Entry(child, textvariable=v_mins, width=2, font='arial 12') e_mins.place(x=225, y=155) v_hrs = tk.StringVar(value='00') e_hrs = tk.Entry(child, textvariable=v_hrs, width=2, font='arial 12') e_hrs.place(x=200, y=155) # 时间设置提示标签 tk.Label(child, font='arial 15 bold', text='set the time',bg='papaya whip').place(x=40, y=150) # 自定义时间启动按钮 x,w = 0,40 tk.Button(child,text='Run',bd='1', command=lambda: start_countdown(child, v_hrs, v_mins, v_secs, None)).place(x=280, y=150) # 快速时间按钮(修复了lambda参数绑定问题) for i,minn in enumerate([1,2,3,4]): tk.Button(child,text=str(minn)+'m', command=lambda m=minn: start_countdown(child, v_hrs, v_mins, v_secs, m*60)).place(x=x+w*i, y=200) win = tk.Tk() menubar = tk.Menu(win) menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Scripts", menu=menu) menu.add_command(label='Countdown',command=lambda : countdown(win)) menu.add_command(label='Countdown 2',command=lambda : countdown(win)) menu.add_command(label='Countdown 3',command=lambda : countdown(win)) win.config(menu=menubar) win.mainloop()
关键改动说明:
- 用
_countdown_step分步执行倒计时,每次执行后通过after()调度下一次任务,完全不阻塞主事件循环。 - 新增
start_countdown作为启动入口,负责解析输入时间并处理输入错误。 - 修复了循环中lambda的参数绑定问题,避免所有快速按钮都使用最后一个时间值。
- 格式化时间显示为两位数字,优化UI体验。
修改后,每个子窗口的倒计时会独立运行,点击菜单也不会导致倒计时暂停,主事件循环始终正常处理所有事件。
内容的提问来源于stack exchange,提问作者dallascow
相关产品推荐
相关产品推荐

