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

Python Tkinter闹钟GUI实现控制台倒计时展示到界面问题

问题原因

Tkinter的UI渲染、事件响应逻辑全部运行在主线程,你直接在按钮回调中执行带time.sleep的死循环,会完全阻塞主线程,所有UI更新操作都会被暂停,因此你尝试修改Label内容的操作不会生效。

解决思路

  1. 将闹钟计时逻辑放到独立子线程运行,避免阻塞主线程UI
  2. 新增绑定StringVar的Label控件,专门用来展示倒计时信息
  3. 子线程中计算当前时间和设定时间的差值,实时更新到绑定的Label变量上即可
  4. 子窗口改用Toplevel()而非新建Tk()实例,避免多主事件循环导致的逻辑异常

修改后完整代码

# Import Required Library
from tkinter import *
import datetime
import time
import winsound
from threading import *
from tkinter import messagebox

def submit():
    # 子窗口改用Toplevel,不要新建Tk实例
    root = Toplevel()
    root.geometry("500x400")
    root.config(bg="#447c84")
    root.title('MathAlarm')

    # 倒计时显示变量
    countdown_var = StringVar()
    countdown_var.set("请设置闹钟时间后点击启动")

    def alarm():
        # Infinite Loop
        while True:
            # Set Alarm
            set_alarm_time = f"{hour.get()}:{minute.get()}:{second.get()}"

            # Wait for one seconds
            time.sleep(1)

            # Get current time
            current_time = datetime.datetime.now().strftime("%H:%M:%S")
            print(current_time,set_alarm_time)

            # 计算剩余时间
            try:
                set_time_obj = datetime.datetime.strptime(set_alarm_time, "%H:%M:%S")
                current_time_obj = datetime.datetime.strptime(current_time, "%H:%M:%S")
                remaining = set_time_obj - current_time_obj
                # 设定时间已过当日的话自动算到次日
                if remaining.total_seconds() < 0:
                    remaining += datetime.timedelta(days=1)
                # 更新倒计时显示,去掉微秒部分
                countdown_var.set(f"剩余时间:{str(remaining).split('.')[0]}\n当前时间:{current_time} | 设定时间:{set_alarm_time}")
            except:
                pass

            # Check whether set alarm is equal to current time or not
            if current_time == set_alarm_time:
                print("Time to Wake up")
                countdown_var.set("时间到!起床啦!")
                # Playing sound
                winsound.PlaySound("sound.wav",winsound.SND_ASYNC)
                messagebox.showinfo(title="ALARM", message="Alarm is going off, its time to wake up!")
                break

    # Add Labels, Frame, Button, Optionmenus
    Label(root,text="Alarm Clock",font=("Helvetica 20 bold"),fg="Black",bg="#447c84").pack(pady=10)
    Label(root,text="Set Time",font=("Helvetica 15 bold"),bg="#447c84").pack()

    frame = Frame(root,bg="#447c84")
    frame.pack()

    hour = StringVar(root)
    hours = ('00', '01', '02', '03', '04', '05', '06', '07',
            '08', '09', '10', '11', '12', '13', '14', '15',
            '16', '17', '18', '19', '20', '21', '22', '23'
            )
    hour.set(hours[0])

    hrs = OptionMenu(frame, hour, *hours)
    hrs.pack(side=LEFT)

    minute = StringVar(root)
    minutes = ('00', '01', '02', '03', '04', '05', '06', '07',
            '08', '09', '10', '11', '12', '13', '14', '15',
            '16', '17', '18', '19', '20', '21', '22', '23',
            '24', '25', '26', '27', '28', '29', '30', '31',
            '32', '33', '34', '35', '36', '37', '38', '39',
            '40', '41', '42', '43', '44', '45', '46', '47',
            '48', '49', '50', '51', '52', '53', '54', '55',
            '56', '57', '58', '59')
    minute.set(minutes[0])

    mins = OptionMenu(frame, minute, *minutes)
    mins.pack(side=LEFT)

    second = StringVar(root)
    seconds = ('00', '01', '02', '03', '04', '05', '06', '07',
            '08', '09', '10', '11', '12', '13', '14', '15',
            '16', '17', '18', '19', '20', '21', '22', '23',
            '24', '25', '26', '27', '28', '29', '30', '31',
            '32', '33', '34', '35', '36', '37', '38', '39',
            '40', '41', '42', '43', '44', '45', '46', '47',
            '48', '49', '50', '51', '52', '53', '54', '55',
            '56', '57', '58', '59')
    second.set(seconds[0])

    secs = OptionMenu(frame, second, *seconds)
    secs.pack(side=LEFT)

    # 倒计时显示Label
    Label(root, textvariable=countdown_var, font=("Helvetica 15 bold"), fg="red", bg="#447c84", justify=CENTER).pack(pady=20)

    # 按钮回调改为启动子线程运行alarm逻辑
    Button(root,text="Set Alarm",font=("Helvetica 15"),command=lambda: Thread(target=alarm, daemon=True).start()).pack(pady=10)
    Button(root,text="Exit",font=("Helvetica 15"), command=lambda:root.destroy()).pack(pady=10)

root = Tk()
root.title('MathAlarm')
root.geometry('347x400')
root.config(bg="#447c84")

welcomelabel = Label(root, text="Welcome to Math Alarm", font=("Times", "24", "bold"), bg="#447c84")
welcomelabel.pack(pady=30)

ext = Button(root, text="Exit", padx=20, pady=10, relief=SOLID, font=("Times", "14", "bold"), command=lambda:root.destroy())
reg = Button(root, text="Create new Alarm", padx=20, pady=10, relief=SOLID, font=("Times", "14", "bold"), command=submit)
reg.pack(pady=20)
ext.pack(pady=20)

# Execute Tkinter
root.mainloop()

补充说明

  • 代码中修正了原来小时、分钟选项里包含24、60的无效值问题
  • 增加了跨零点的时间计算逻辑,设定时间晚于当前时间时会自动计算到次日的剩余时间
  • 子线程设置为daemon守护线程,关闭窗口时会自动终止计时逻辑

内容的提问来源于stack exchange,提问作者ChrisBoss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 13:54:03