前言

上一次我们用 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,未经许可禁止转载。

更多推荐