You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:10:28