python计时器
2026-06-15 19:01:44
发布于:广东
AI生成
import tkinter as tk
from tkinter import ttk, messagebox
import time
import threading
import datetime
import winsound
class TimerApp:
def __init__(self, root):
self.root = root
self.root.title("多功能计时器")
self.root.geometry("400x250")
self.root.resizable(False, False)
# ── 计时状态 ──
self.running = False
self.mode = "clock" # clock / countup / countdown / alarm
self.start_time = 0.0
self.elapsed = 0.0
self.remaining = 0.0
# ── 闹钟列表 ──
self.alarms = [] # [{"time": datetime, "label": str}]
# ── 缩小模式 & 全屏 ──
self.mini_window = None
self.is_fullscreen = False
self.create_widgets()
self.update_clock()
self.check_alarms()
# ── 快捷键 ──
self.root.bind("<Alt-Return>", self.toggle_fullscreen)
self.root.bind("<Escape>", self.exit_fullscreen)
# ═══════════════════════════════════════════
# UI 构建
# ═══════════════════════════════════════════
def create_widgets(self):
# ── 1. 时间显示(非闹钟模式可见) ──
self.display_var = tk.StringVar(value=self.get_current_time())
self.display_label = tk.Label(
self.root, textvariable=self.display_var,
font=("Helvetica", 48), fg="black"
)
self.display_label.pack(pady=(20, 5))
# ── 2. 闹钟列表容器(闹钟模式可见) ──
self.alarm_container = tk.Frame(self.root)
add_row = tk.Frame(self.alarm_container)
add_row.pack(fill=tk.X, pady=(10, 2), padx=20)
tk.Label(add_row, text="时").pack(side=tk.LEFT)
self.al_hour = tk.Spinbox(add_row, from_=0, to=23, width=3, state="readonly")
self.al_hour.pack(side=tk.LEFT, padx=2)
tk.Label(add_row, text="分").pack(side=tk.LEFT)
self.al_min = tk.Spinbox(add_row, from_=0, to=59, width=3, state="readonly")
self.al_min.pack(side=tk.LEFT, padx=2)
tk.Label(add_row, text="秒").pack(side=tk.LEFT)
self.al_sec = tk.Spinbox(add_row, from_=0, to=59, width=3, state="readonly")
self.al_sec.pack(side=tk.LEFT, padx=2)
list_frame = tk.Frame(self.alarm_container)
list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=5)
scrollbar = tk.Scrollbar(list_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.alarm_listbox = tk.Listbox(
list_frame, height=4, yscrollcommand=scrollbar.set,
font=("Consolas", 12)
)
self.alarm_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.alarm_listbox.yview)
self.alarm_container.pack_forget() # 默认隐藏
# ── 3. 模式选择 ──
self.mode_frame = tk.Frame(self.root)
self.mode_frame.pack(pady=5)
self.mode_var = tk.StringVar(value="clock")
modes = [
("当前时间", "clock"),
("正计时", "countup"),
("倒计时", "countdown"),
("闹钟", "alarm"),
]
for text, mode in modes:
rb = tk.Radiobutton(
self.mode_frame, text=text, variable=self.mode_var,
value=mode, command=self.switch_mode
)
rb.pack(side=tk.LEFT, padx=5)
# ── 4. 设置区(倒计时用) ──
self.set_frame = tk.Frame(self.root)
tk.Label(self.set_frame, text="时").grid(row=0, column=0)
self.hour_var = tk.StringVar(value="0")
self.hour_spin = tk.Spinbox(
self.set_frame, from_=0, to=23, width=3,
textvariable=self.hour_var, state="readonly"
)
self.hour_spin.grid(row=0, column=1)
tk.Label(self.set_frame, text="分").grid(row=0, column=2)
self.min_var = tk.StringVar(value="0")
self.min_spin = tk.Spinbox(
self.set_frame, from_=0, to=59, width=3,
textvariable=self.min_var, state="readonly"
)
self.min_spin.grid(row=0, column=3)
tk.Label(self.set_frame, text="秒").grid(row=0, column=4)
self.sec_var = tk.StringVar(value="0")
self.sec_spin = tk.Spinbox(
self.set_frame, from_=0, to=59, width=3,
textvariable=self.sec_var, state="readonly"
)
self.sec_spin.grid(row=0, column=5)
self.set_frame.pack_forget()
# ── 5. 控制按钮 ──
self.btn_frame = tk.Frame(self.root)
# 计时按钮
self.start_btn = tk.Button(
self.btn_frame, text="开始", command=self.start,
width=8, state=tk.DISABLED
)
self.start_btn.grid(row=0, column=0, padx=5)
self.pause_btn = tk.Button(
self.btn_frame, text="暂停", command=self.pause,
width=8, state=tk.DISABLED
)
self.pause_btn.grid(row=0, column=1, padx=5)
self.reset_btn = tk.Button(
self.btn_frame, text="重置", command=self.reset,
width=8, state=tk.DISABLED
)
self.reset_btn.grid(row=0, column=2, padx=5)
# 闹钟按钮
self.add_alarm_btn = tk.Button(
self.btn_frame, text="添加", command=self.add_alarm,
width=10, bg="#2196F3", fg="white"
)
self.add_alarm_btn.grid(row=0, column=0, padx=5)
self.del_alarm_btn = tk.Button(
self.btn_frame, text="删除", command=self.delete_alarm,
width=10, bg="#f44336", fg="white"
)
self.del_alarm_btn.grid(row=0, column=1, padx=5)
self.del_alarm_btn.grid_remove()
self.add_alarm_btn.grid_remove()
self.btn_frame.pack(pady=5)
self._show_timer_buttons()
# ── 6. 缩小模式 ──
self.mini_btn = tk.Button(
self.root, text="缩小模式", command=self.open_mini_window, width=10
)
self.mini_btn.pack(side=tk.BOTTOM, pady=5, anchor=tk.SE, padx=10)
# ═══════════════════════════════════════════
# 按钮切换
# ═══════════════════════════════════════════
def _show_timer_buttons(self):
self.add_alarm_btn.grid_remove()
self.del_alarm_btn.grid_remove()
self.start_btn.grid()
self.pause_btn.grid()
self.reset_btn.grid()
def _show_alarm_buttons(self):
self.start_btn.grid_remove()
self.pause_btn.grid_remove()
self.reset_btn.grid_remove()
self.add_alarm_btn.grid()
self.del_alarm_btn.grid()
# ═══════════════════════════════════════════
# 模式切换
# ═══════════════════════════════════════════
def switch_mode(self):
mode = self.mode_var.get()
self.mode = mode
self.reset()
if self.is_fullscreen:
return
# 先隐藏所有可变区域
self.display_label.pack_forget()
self.alarm_container.pack_forget()
self.set_frame.pack_forget()
if mode == "alarm":
self.alarm_container.pack(
after=self.mode_frame, fill=tk.BOTH,
expand=True, padx=5, pady=5
)
self._show_alarm_buttons()
self.root.geometry("400x500")
else:
self.display_label.pack(before=self.mode_frame, pady=(20, 5))
if mode == "countdown":
self.set_frame.pack(after=self.mode_frame, pady=5)
self._show_timer_buttons()
self.root.geometry("400x250")
# 按钮状态
if mode in ("countup", "countdown"):
self.start_btn.config(state=tk.NORMAL)
else:
self.start_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.DISABLED)
self.reset_btn.config(state=tk.DISABLED)
# ═══════════════════════════════════════════
# 计时控制
# ═══════════════════════════════════════════
def get_current_time(self):
return datetime.datetime.now().strftime("%H:%M:%S")
def start(self):
if self.mode == "countup":
self.running = True
self.start_time = time.perf_counter() - self.elapsed
elif self.mode == "countdown":
try:
h = int(self.hour_var.get())
m = int(self.min_var.get())
s = int(self.sec_var.get())
self.remaining = h * 3600 + m * 60 + s
if self.remaining <= 0:
messagebox.showwarning("警告", "请设置大于0的倒计时时间")
return
self.running = True
self.start_time = time.perf_counter()
except ValueError:
return
self.start_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.NORMAL)
self.reset_btn.config(state=tk.NORMAL)
def pause(self):
self.running = False
if self.mode == "countup":
self.elapsed = time.perf_counter() - self.start_time
elif self.mode == "countdown":
elapsed = time.perf_counter() - self.start_time
self.remaining -= elapsed
if self.remaining < 0:
self.remaining = 0
self.start_btn.config(state=tk.NORMAL)
self.pause_btn.config(state=tk.DISABLED)
def reset(self):
self.running = False
if self.mode == "countup":
self.elapsed = 0.0
elif self.mode == "countdown":
self.remaining = 0.0
if self.mode in ("countup", "countdown"):
self.start_btn.config(state=tk.NORMAL)
else:
self.start_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.DISABLED)
self.reset_btn.config(state=tk.DISABLED)
# ═══════════════════════════════════════════
# 时钟刷新
# ═══════════════════════════════════════════
def update_clock(self):
if self.mode == "clock" or self.mode == "alarm":
current = self.get_current_time()
self.display_var.set(current)
if self.mini_window and self.mini_window.winfo_exists():
self.mini_display_var.set(current)
elif self.mode == "countup":
if self.running:
self.elapsed = time.perf_counter() - self.start_time
display = time.strftime("%H:%M:%S", time.gmtime(self.elapsed))
self.display_var.set(display)
if self.mini_window and self.mini_window.winfo_exists():
self.mini_display_var.set(display)
elif self.mode == "countdown":
if self.running:
elapsed = time.perf_counter() - self.start_time
remaining = self.remaining - elapsed
if remaining <= 0:
remaining = 0
self.running = False
self.start_btn.config(state=tk.NORMAL)
self.pause_btn.config(state=tk.DISABLED)
threading.Thread(target=self.play_sound, daemon=True).start()
display = time.strftime(
"%H:%M:%S", time.gmtime(max(0, remaining))
)
else:
display = time.strftime(
"%H:%M:%S", time.gmtime(max(0, self.remaining))
)
self.display_var.set(display)
if self.mini_window and self.mini_window.winfo_exists():
self.mini_display_var.set(display)
self.root.after(200, self.update_clock)
# ═══════════════════════════════════════════
# 闹钟管理
# ═══════════════════════════════════════════
def add_alarm(self):
try:
h = int(self.al_hour.get())
m = int(self.al_min.get())
s = int(self.al_sec.get())
if not (0 <= h <= 23 and 0 <= m <= 59 and 0 <= s <= 59):
raise ValueError
except ValueError:
messagebox.showwarning(
"错误", "请输入有效的时间(时:0-23, 分:0-59, 秒:0-59)"
)
return
now = datetime.datetime.now()
alarm_time = now.replace(hour=h, minute=m, second=s, microsecond=0)
if alarm_time <= now:
alarm_time += datetime.timedelta(days=1)
self.alarms.append({
"time": alarm_time,
"label": alarm_time.strftime("%H:%M:%S"),
})
self.refresh_alarm_listbox()
def delete_alarm(self):
selection = self.alarm_listbox.curselection()
if not selection:
messagebox.showinfo("提示", "请先选中一个闹钟")
return
idx = selection[0]
if idx < len(self.alarms):
del self.alarms[idx]
self.refresh_alarm_listbox()
def refresh_alarm_listbox(self):
self.alarm_listbox.delete(0, tk.END)
for al in self.alarms:
self.alarm_listbox.insert(tk.END, al["label"])
def check_alarms(self):
now = datetime.datetime.now()
triggered = []
for i, al in enumerate(self.alarms):
if now >= al["time"]:
triggered.append(i)
for i in reversed(triggered):
al = self.alarms[i]
threading.Thread(target=self.play_sound, daemon=True).start()
self.root.after(
0,
lambda t=al["time"]: messagebox.showinfo(
"闹钟响了!", f"⏰ {t.strftime('%H:%M:%S')} 到啦!"
),
)
del self.alarms[i]
if triggered:
self.refresh_alarm_listbox()
self.root.after(1000, self.check_alarms)
def play_sound(self):
for _ in range(3):
winsound.Beep(1000, 500)
time.sleep(0.2)
# ═══════════════════════════════════════════
# 全屏
# ═══════════════════════════════════════════
def _hide_controls(self):
for w in [
self.display_label, self.mode_frame, self.set_frame,
self.btn_frame, self.alarm_container, self.mini_btn,
]:
w.pack_forget()
def _show_controls(self):
# 清理所有可变区域
self.display_label.pack_forget()
self.alarm_container.pack_forget()
self.set_frame.pack_forget()
self.mode_frame.pack(pady=5)
if self.mode == "alarm":
self.alarm_container.pack(
after=self.mode_frame, fill=tk.BOTH,
expand=True, padx=5, pady=5
)
self._show_alarm_buttons()
self.root.geometry("400x500")
else:
self.display_label.pack(before=self.mode_frame, pady=(20, 5))
if self.mode == "countdown":
self.set_frame.pack(after=self.mode_frame, pady=5)
self._show_timer_buttons()
self.root.geometry("400x360")
self.btn_frame.pack(pady=5, after=self.mode_frame)
self.mini_btn.pack(side=tk.BOTTOM, pady=5, anchor=tk.SE, padx=10)
def toggle_fullscreen(self, event=None):
self.is_fullscreen = not self.is_fullscreen
self.root.attributes("-fullscreen", self.is_fullscreen)
if self.is_fullscreen:
self._hide_controls()
self.display_label.config(font=("Helvetica", 130))
self.display_label.pack_forget()
self.display_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
else:
self.display_label.place_forget()
self.display_label.config(font=("Helvetica", 48))
self._show_controls()
def exit_fullscreen(self, event=None):
if self.is_fullscreen:
self.is_fullscreen = False
self.root.attributes("-fullscreen", False)
self.display_label.place_forget()
self.display_label.config(font=("Helvetica", 48))
self._show_controls()
# ═══════════════════════════════════════════
# 缩小模式
# ═══════════════════════════════════════════
def open_mini_window(self):
if self.mini_window and self.mini_window.winfo_exists():
self.mini_window.lift()
return
self.root.withdraw()
self.mini_window = tk.Toplevel(self.root)
self.mini_window.overrideredirect(True)
self.mini_window.attributes("-topmost", True)
self.mini_window.configure(bg="white")
self.mini_window.geometry("160x50+100+100")
self.mini_window.bind("<Destroy>", self._on_mini_destroy)
# 闹钟模式下缩小窗口显示当前时间
if self.mode == "alarm":
text = self.get_current_time()
else:
text = self.display_var.get()
self.mini_display_var = tk.StringVar(value=text)
mini_label = tk.Label(
self.mini_window, textvariable=self.mini_display_var,
font=("Helvetica", 20, "bold"), bg="black", fg="white"
)
mini_label.pack(fill=tk.BOTH, expand=True, padx=3, pady=3)
mini_label.bind("<Button-1>", self.start_move)
mini_label.bind("<B1-Motion>", self.do_move)
mini_label.bind(
"<Double-Button-1>", lambda e: self.close_mini_window()
)
self.mini_window.bind(
"<Button-3>", lambda e: self.close_mini_window()
)
def _on_mini_destroy(self, event):
if event.widget == self.mini_window:
self.mini_window = None
self.root.deiconify()
def start_move(self, event):
self._drag_x = event.x
self._drag_y = event.y
def do_move(self, event):
deltax = event.x - self._drag_x
deltay = event.y - self._drag_y
new_x = self.mini_window.winfo_x() + deltax
new_y = self.mini_window.winfo_y() + deltay
self.mini_window.geometry(f"+{new_x}+{new_y}")
def close_mini_window(self):
if self.mini_window:
self.mini_window.destroy()
self.mini_window = None
self.root.deiconify()
if self.is_fullscreen:
self.root.attributes("-fullscreen", True)
if __name__ == "__main__":
root = tk.Tk()
app = TimerApp(root)
root.mainloop()
这里空空如也





















有帮助,赞一个