当前位置: 首页 > news >正文

Python tkinter 番茄钟实战(二):25分钟专注计时器,带桌面置顶与提示音

前言

上一次我们用 tkinter 写了一个简单的记账本,有读者反馈说图形界面编程比想象中的要更有趣。这一次,我们把目标稍微提高一点,做一个更加实用的桌面小工具——番茄钟 + 便签二合一。

这个工具具备以下特点:
- 25分钟专注倒计时,时间可自定义。
- 窗口始终置顶,不会被其他应用遮挡。
- 计时结束自动弹窗,并播放系统提示音。
- 附带一个简单的便签区域,文字内容会自动保存,关闭后重新打开依然存在。

代码总量约 150 行,结构清晰,注释详尽。你也可以直接跳到文章末尾获取完整代码和打包好的 exe 文件。


一、最终效果预览

程序启动后是一个长条形的悬浮窗,上半部分显示倒计时,下半部分是一个可编辑的文本框。你可以把它拖到屏幕角落,一边工作一边记录临时想法。


二、功能拆解与实现思路

1. 界面布局
界面使用 tkinter 的 Frame 进行模块划分,主要包含三个区域:
- 计时显示区:Label 组件,显示剩余时间,字体较大。
- 控制按钮区:开始、暂停、重置三个按钮。
- 便签编辑区:Text 组件,用于记录文字。

2. 计时器核心逻辑
tkinter 的计时功能依赖 after(ms, callback) 方法。该方法会在指定毫秒数后调用一次回调函数。在回调函数内部,我们减少一秒并更新显示,然后再次调用 after,形成循环。

3. 窗口置顶
置顶功能只需要一行代码:
self.root.attributes('-topmost', True)

4. 数据持久化
便签内容会在程序关闭时自动保存到 note.txt 文件中,下次启动时自动读取并显示。


三、完整代码实现

将以下代码保存为 pomodoro.py,直接运行即可。

import tkinter as tk
from tkinter import messagebox
import os
import winsound # Windows 系统提示音,Mac/Linux 需替换

class PomodoroApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("番茄钟 · 便签")
self.root.geometry("300x250")
self.root.resizable(False, False)
self.root.attributes('-topmost', True) # 桌面置顶

# 计时相关变量
self.default_time = 25 * 60 # 25分钟,单位秒
self.remaining = self.default_time
self.is_running = False
self.after_id = None

# 便签文件路径
self.note_file = "note.txt"

self.setup_ui()
self.load_note()
self.update_timer_display()

def setup_ui(self):
# 计时显示标签
self.time_label = tk.Label(self.root, text="", font=("微软雅黑", 36), fg="#2c3e50")
self.time_label.pack(pady=10)

# 按钮框架
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=5)

self.start_btn = tk.Button(btn_frame, text="开始", width=8, command=self.start_timer)
self.start_btn.pack(side=tk.LEFT, padx=5)

self.pause_btn = tk.Button(btn_frame, text="暂停", width=8, command=self.pause_timer, state=tk.DISABLED)
self.pause_btn.pack(side=tk.LEFT, padx=5)

self.reset_btn = tk.Button(btn_frame, text="重置", width=8, command=self.reset_timer)
self.reset_btn.pack(side=tk.LEFT, padx=5)

# 自定义时间输入(秒)
input_frame = tk.Frame(self.root)
input_frame.pack(pady=5)

tk.Label(input_frame, text="时长(秒):").pack(side=tk.LEFT)
self.time_entry = tk.Entry(input_frame, width=8)
self.time_entry.insert(0, str(self.default_time))
self.time_entry.pack(side=tk.LEFT, padx=5)

set_btn = tk.Button(input_frame, text="设置", command=self.set_custom_time)
set_btn.pack(side=tk.LEFT)

# 便签区域
tk.Label(self.root, text="便签 (自动保存)").pack(anchor=tk.W, padx=10, pady=(5,0))
self.note_text = tk.Text(self.root, height=5, width=35)
self.note_text.pack(padx=10, pady=5)

# 绑定关闭事件,保存便签
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

def load_note(self):
"""加载本地便签内容"""
if os.path.exists(self.note_file):
with open(self.note_file, "r", encoding="utf-8") as f:
content = f.read()
self.note_text.insert("1.0", content)

def save_note(self):
"""保存便签内容到本地文件"""
content = self.note_text.get("1.0", tk.END).strip()
with open(self.note_file, "w", encoding="utf-8") as f:
f.write(content)

def on_closing(self):
"""窗口关闭时保存便签并销毁窗口"""
self.save_note()
self.root.destroy()

def update_timer_display(self):
"""更新计时器显示格式 mm:ss"""
minutes = self.remaining // 60
seconds = self.remaining % 60
self.time_label.config(text=f"{minutes:02d}:{seconds:02d}")

def start_timer(self):
"""开始倒计时"""
if not self.is_running and self.remaining > 0:
self.is_running = True
self.start_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.NORMAL)
self.countdown()

def countdown(self):
"""倒计时核心逻辑"""
if self.is_running and self.remaining > 0:
self.remaining -= 1
self.update_timer_display()
self.after_id = self.root.after(1000, self.countdown)
elif self.remaining == 0:
self.timer_finished()

def pause_timer(self):
"""暂停计时"""
if self.is_running:
self.is_running = False
self.start_btn.config(state=tk.NORMAL)
self.pause_btn.config(state=tk.DISABLED)
if self.after_id:
self.root.after_cancel(self.after_id)

def reset_timer(self):
"""重置计时器(从默认时间重新开始)"""
self.pause_timer()
self.remaining = self.default_time
self.update_timer_display()

def set_custom_time(self):
"""设置自定义时长(秒)"""
try:
new_time = int(self.time_entry.get())
if new_time > 0:
self.default_time = new_time
self.remaining = new_time
self.update_timer_display()
messagebox.showinfo("提示", f"已设置时长为 {new_time} 秒")
else:
messagebox.showerror("错误", "时长必须为正整数")
except ValueError:
messagebox.showerror("错误", "请输入有效的整数")

def timer_finished(self):
"""计时结束时的动作"""
self.is_running = False
self.start_btn.config(state=tk.NORMAL)
self.pause_btn.config(state=tk.DISABLED)
self.update_timer_display()
# 弹窗提示
messagebox.showinfo("番茄钟", "时间到!休息一下吧。")
# 播放系统提示音(Windows)
try:
winsound.MessageBeep(winsound.MB_ICONEXCLAMATION)
except:
pass
# 重置为默认时间,便于下一轮开始
self.remaining = self.default_time
self.update_timer_display()

def run(self):
self.root.mainloop()

if __name__ == "__main__":
app = PomodoroApp()
app.run()

四、功能测试与效果展示

程序启动后,我们可以快速验证几个核心功能是否正常工作:

1. 点击「开始」按钮,倒计时从 25:00 开始每秒递减,按钮状态变为「暂停」可用、「开始」禁用。
2. 点击「暂停」后计时停止,再次点击「开始」则从剩余时间继续倒计时。
3. 点击「重置」按钮,无论计时处于何种状态,都会恢复为预设的 25:00。
4. 在输入框中输入任意秒数(例如 10),点击「设置」,会弹出提示框确认修改,同时倒计时显示更新为对应时长。
5. 在便签区域随意输入一些文字,关闭窗口后重新打开程序,文字仍然存在(自动保存到本地 `note.txt` 文件)。

经过以上测试,所有功能均运行正常。接下来我们将从界面布局开始,逐步拆解代码实现。

五、核心代码讲解

1. 窗口置顶
self.root.attributes('-topmost', True)
设置 topmost 属性后,窗口将始终显示在其他普通窗口的上方,非常适合番茄钟这类需要随时查看进度的工具。

2. 倒计时的实现
def countdown(self):
if self.is_running and self.remaining > 0:
self.remaining -= 1
self.update_timer_display()
self.after_id = self.root.after(1000, self.countdown)
elif self.remaining == 0:
self.timer_finished()
after(1000, self.countdown) 的含义是:1 秒后调用 countdown 函数自身。只要 is_running 为 True 且时间大于零,就会每秒减一并更新显示。

3. 便签自动保存
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
这行代码拦截了窗口关闭事件,确保在关闭前执行 on_closing 方法,将 Text 组件中的内容写入 note.txt。


六、打包为独立的 exe 文件

如果你希望将程序分享给没有安装 Python 的朋友,可以使用 PyInstaller 打包。

1. 安装 PyInstaller:
pip install pyinstaller

2. 在 pomodoro.py 所在目录下打开终端,执行:
pyinstaller -F -w pomodoro.py

3. 打包完成后,dist 文件夹中会生成 pomodoro.exe,双击即可运行。


七、后续改进方向

- 增加数据统计:记录每天完成的番茄数量,并用 matplotlib 绘制专注曲线。
- 添加系统托盘:最小化到托盘,不占用任务栏空间。
- 跨平台适配:Mac / Linux 下提示音的实现方式不同,可后续兼容。

这些内容我会在后续的博文中逐一实现,欢迎关注本系列。


八、写在最后

如果你跟着教程一步步操作,现在你应该已经有了一个属于自己的桌面番茄钟了。

一个小问题:你平常习惯的番茄时长是 25 分钟,还是自定义的其他时长?运行环境是 Windows 还是 Mac?评论区告诉我,我会根据大家的反馈来调整下一版的功能。

如果在运行过程中遇到任何报错,也欢迎在评论区留言,我会一一回复。

本文原创发布于 CSDN,作者 qinrunlin,未经许可禁止转载。

http://www.jsqmd.com/news/651995/

相关文章:

  • 2026届必备的十大AI学术方案实际效果
  • Golang map底层实现原理_Golang map哈希表原理教程【收藏】
  • 进化算法新突破:图解L-SHADE中的线性种群缩减机制
  • Zephyr RTOS线程优化指南:如何避免常见性能陷阱与资源浪费
  • R 语言实战:运用 BIOMOD2 包构建、评估并集成物种分布模型
  • CAN收发器选型避坑指南:TJA1051T与TJA1051T/3的硬件兼容性问题实录
  • wiliwili:让游戏主机变身全能B站客户端的跨平台实践
  • 告别Activity监听!用ProcessLifecycleOwner在Application里统一管理App前后台(附完整Kotlin代码)
  • PCIe带宽计算实战:从GT/s到实际传输速率的完整换算指南
  • 捷联惯导姿态更新算法探析:从毕卡、龙格库塔到精确数值解法的工程实践
  • Claude+Go实战:我是如何用AI自动生成完整Makefile的(含避坑指南)
  • 别再乱用`define`了!SystemVerilog枚举类型(enum)的五大进阶用法与避坑指南
  • 2025年网盘下载太慢?8大网盘直链下载工具LinkSwift完整解决方案
  • 全面解析:如何深度解锁索尼相机隐藏功能的逆向工程指南
  • CVPR 2024 视频理解技术全景解析:从监控到多模态交互
  • 图像变化检测技术在军事毁伤评估中的实战应用解析
  • 别再怕高维张量了!用Python手把手实现TT分解,5分钟搞定图像压缩
  • 一键永久保存QQ空间记忆:GetQzonehistory免费工具终极备份指南
  • 消息队列选型指南
  • Qt for Android:基于libusb实现CH340x串口通信的高效开发方案
  • 28 Nginx的http块MIME-Type的使用
  • 避开这些坑!蓝桥杯Python研究生组备赛常见误区与实战技巧
  • 计算机类 18 个专业全解读!一文搞懂选专业 + 就业方向
  • 深入解析MOS管米勒效应及其对开关损耗的影响
  • 5分钟掌握foobar2000歌词插件OpenLyrics:打造专业音乐播放体验
  • EPLAN拖放操作避坑指南:从符号宏到DWG导入,这些细节错了白忙活
  • 如何高效管理Chrome书签:Neat Bookmarks树状扩展完整指南
  • Linux下Questasim 10.7c保姆级安装与首次仿真避坑指南
  • UE5 反射系统
  • 突破Linux无线网络困局:Realtek 8851BE驱动深度调优指南