admin管理员组文章数量:1437110
PyQt5 番茄钟实现
前言
最近,一直被效率焦虑困扰:工作时老是分心,时不时打开社交软件,打断了专注,结果一天下来干活像散沙。脑海里忽然闪过一个念头:如果有个桌面定时提醒器,能在指定时间提醒我休息、做番茄钟,甚至弹出一句鼓励的小语,岂不是美滋滋?更何况,我早就想借此练练 PyQt,打造一个属于自己的“效率小助手”。
于是,这篇博客就这样诞生了:我决定利用 PyQt5/6,从零开始实现一款 定时提醒器/番茄钟,包含设置界面、倒计时逻辑、系统通知、声音提醒、系统托盘最小化、持久化配置等功能。文章将讲述我在设计、编码、调试、打包过程中踩过的坑和收获的经验,通过流程图,让你一目了然模块之间的交互。无论你是 PyQt 小白还是想做桌面工具的同学,都能在本文找到灵感。
一、需求与动机
我给自己列了几个“非功能化”需求和“核心”需求:不想写成一堆分点,简单说说当时的想法。每天工作二十五分钟,我需要一个番茄钟,计时结束能提醒我休息五分钟;休息结束时,又需要提醒我继续工作;我还希望能自定义工作时长和休息时长;如果需要其它定时提醒(比如下午三点喝茶),最好也能在同一界面新增;程序最小化到系统托盘后还能继续运行,不占任务栏;最后,提醒时要有声音和桌面通知,保证视听双重反馈。想到这些,我就迫不及待想动手了。
二、为什么用 PyQt
说到桌面应用开发,Python 除了 PyQt,还有 wxPython、Tkinter,甚至 Electron。但是 Electron 打包庞大,网络性能不稳定;Tkinter 界面简陋;wxPython 学习成本也不低。相比之下,PyQt 具备以下优势:
- 信号槽机制清晰,事件驱动极易管理定时器、按钮回调;
- Qt 自带丰富的控件,布局管理直观;
- QTimer 定时器类让时间相关逻辑更简洁;
- 可以通过 QSystemTrayIcon 实现系统托盘;
- 丰富的样式表(QSS)支持美化 UI;
- PyInstaller 打包简单,无需用户预装 Python。
下定决心后,我在本地新建了项目目录 pyqt_timer/
,并安装依赖:
pip install PyQt5 # 或 PyQt6,根据需求
pip install plyer # 用于跨平台通知(可选)
三、架构与模块设计
着手编码前,我习惯先手绘或画个模块交互图,让思路更清晰。这里用画一下核心流程:
大致分为以下模块:
- MainWindow:主窗口,负责 UI 交互和信号连接;
- TimerController:定时器逻辑,封装
QTimer
,控制番茄钟和自定义提醒; - ConfigManager:配置管理,加载/保存用户设置(JSON 文件);
- NotificationManager:通知管理,桌面消息和声音提醒;
- SystemTray:系统托盘图标,支持最小化与恢复;
- SoundPlayer:播放本地提示音(可扩展到自定义铃声)。
有了总体规划,接下来就分模块落地。
四、项目目录结构
在决定好模块之后,我在 pyqt_timer/
目录下创建子文件:
pyqt_timer/
├── main.py
├── main_window.py
├── timer_controller.py
├── config_manager.py
├── notification_manager.py
├── system_tray.py
├── sound_player.py
├── resources/
│ ├── icons/
│ │ ├── play.png
│ │ ├── pause.png
│ │ ├── reset.png
│ │ ├── tray.png
│ │ └── settings.png
│ ├── style.qss
│ └── alert.wav
└── config.json # 程序第一次运行后自动生成
resources/
存放图标、样式表和提示音。config.json
存储用户设置。
接着,用 pyrcc5
将 resources/
打包到 resources_rc.py
,方便程序内引用。
五、配置管理 ConfigManager
为了让用户设置持久化,我设计了一个轻量的配置管理类,用 JSON 存储。简单说:启动时读一份配置文件(若不存在,则写入默认配置);更改设置后保存到同一个文件。
代码语言:python代码运行次数:0运行复制# config_manager.py
import json
import os
class ConfigManager:
def __init__(self, path="config.json"):
self.path = path
self.config = {
"work_duration": 25 * 60,
"break_duration": 5 * 60,
"custom_reminders": [] # [{"time": "15:00", "label": "喝茶"}]
}
self.load()
def load(self):
if os.path.exists(self.path):
try:
with open(self.path, "r", encoding="utf-8") as f:
data = json.load(f)
self.config.update(data)
except Exception as e:
print(f"加载配置失败:{e}")
def save(self):
try:
with open(self.path, "w", encoding="utf-8") as f:
json.dump(self.config, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"保存配置失败:{e}")
def get(self, key):
return self.config.get(key)
def set(self, key, value):
self.config[key] = value
self.save()
这里要注意:JSON 文件写入时,需要 ensure_ascii=False
保证中文配置不被转义。
六、定时器逻辑 TimerController
定时器的核心是 QTimer。我将它封装到 TimerController
类,负责番茄钟的“工作/休息”状态切换、自定义多提醒点,以及每秒通知主界面更新剩余时间。
# timer_controller.py
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, QTime
class TimerController(QObject):
tick = pyqtSignal(int) # 剩余秒数信号
phase_changed = pyqtSignal(str) # "work" 或 "break"
reminder_triggered = pyqtSignal(str) # 自定义提醒标签
def __init__(self, config):
super().__init__()
self.config = config
self.timer = QTimer(self)
self.timer.timeout.connect(self._on_timeout)
self._init_state()
def _init_state(self):
self.phase = "work"
self.remaining = self.config.get("work_duration")
self.custom = self._parse_custom()
self.timer.start(1000)
self.tick.emit(self.remaining)
self.phase_changed.emit(self.phase)
def _parse_custom(self):
lst = []
for item in self.config.get("custom_reminders"):
try:
t = QTime.fromString(item["time"], "HH:mm")
sec = t.hour() * 3600 + t.minute() * 60
lst.append((sec, item["label"]))
except:
continue
return lst
def _on_timeout(self):
self.remaining -= 1
now = QTime.currentTime()
cur_sec = now.hour() * 3600 + now.minute() * 60 + now.second()
for sec, label in self.custom:
if cur_sec == sec:
self.reminder_triggered.emit(label)
if self.remaining <= 0:
if self.phase == "work":
self.phase = "break"
self.remaining = self.config.get("break_duration")
else:
self.phase = "work"
self.remaining = self.config.get("work_duration")
self.phase_changed.emit(self.phase)
self.tick.emit(self.remaining)
def start(self):
if not self.timer.isActive():
self.timer.start(1000)
def pause(self):
if self.timer.isActive():
self.timer.stop()
def reset(self):
self.timer.stop()
self._init_state()
这个设计带来几个好处:主界面只需要连接三个信号,就能完成时间显示、状态切换和任意时刻的自定义提醒。
七、主界面 MainWindow
UI 部分最耗心思:要同时展示番茄钟倒计时区、自定义提醒列表和设置按钮,还要兼容最小化到托盘。以下是简化版核心逻辑。
代码语言:python代码运行次数:0运行复制# main_window.py
from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QListWidget, QHBoxLayout, QVBoxLayout, QWidget, QLineEdit, QTimeEdit
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from timer_controller import TimerController
from config_manager import ConfigManager
from notification_manager import NotificationManager
from system_tray import SystemTray
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("定时提醒器 / 番茄钟")
self.config = ConfigManager()
self.notifier = NotificationManager()
self.tray = SystemTray(self)
self._init_ui()
self._connect_signals()
self.timer = TimerController(self.config)
def _init_ui(self):
self.label_time = QLabel("25:00")
self.label_time.setAlignment(Qt.AlignCenter)
self.btn_start = QPushButton(QIcon(":/icons/play.png"), "")
self.btn_pause = QPushButton(QIcon(":/icons/pause.png"), "")
self.btn_reset = QPushButton(QIcon(":/icons/reset.png"), "")
self.list_custom = QListWidget()
self.time_edit = QTimeEdit(self)
self.time_edit.setDisplayFormat("HH:mm")
self.line_label = QLineEdit(self)
self.btn_add = QPushButton("新增提醒")
top = QHBoxLayout()
top.addWidget(self.label_time)
top.addWidget(self.btn_start)
top.addWidget(self.btn_pause)
top.addWidget(self.btn_reset)
bottom = QHBoxLayout()
bottom.addWidget(self.time_edit)
bottom.addWidget(self.line_label)
bottom.addWidget(self.btn_add)
main = QVBoxLayout()
main.addLayout(top)
main.addWidget(self.list_custom)
main.addLayout(bottom)
container = QWidget()
container.setLayout(main)
self.setCentralWidget(container)
self.resize(400, 300)
def _connect_signals(self):
self.btn_start.clicked.connect(self.timer.start)
self.btn_pause.clicked.connect(self.timer.pause)
self.btn_reset.clicked.connect(self._on_reset)
self.btn_add.clicked.connect(self._add_custom)
self.timer.tick.connect(self._update_time)
self.timer.phase_changed.connect(self._on_phase_change)
self.timer.reminder_triggered.connect(self._on_custom_trigger)
self.tray.activated.connect(self._on_tray_activated)
def _update_time(self, sec):
m, s = divmod(sec, 60)
self.label_time.setText(f"{m:02d}:{s:02d}")
def _on_phase_change(self, phase):
if phase == "work":
self.notifier.notify("工作时间到,开始专注!")
else:
self.notifier.notify("休息时间到,放松一下!")
def _add_custom(self):
t = self.time_edit.time().toString("HH:mm")
lab = self.line_label.text().strip()
if not lab:
return
lst = self.config.get("custom_reminders")
lst.append({"time": t, "label": lab})
self.config.set("custom_reminders", lst)
self.list_custom.addItem(f"{t} – {lab}")
def _on_custom_trigger(self, label):
self.notifier.notify(f"自定义提醒:{label}")
def _on_reset(self):
self.timer.reset()
def closeEvent(self, event):
event.ignore()
self.hide()
self.tray.showMessage("定时提醒器已最小化到托盘", "双击图标可恢复窗口")
要点提示:在 closeEvent
中使用 event.ignore()
而不是 super().closeEvent(event)
,才能让窗口真正隐藏而非退出程序。
八、通知管理 NotificationManager
如何让提醒更醒目?我同时做了 桌面气泡 和 声音。桌面通知我用 QSystemTrayIcon.showMessage
,声音用 QSound
(或第三方 playsound
/plyer
)。示例:
# notification_manager.py
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QIcon, QSystemTrayIcon
from PyQt5.QtMultimedia import QSound
import resources_rc
class NotificationManager:
def __init__(self):
self.tray = QSystemTrayIcon(QIcon(":/icons/tray.png"), QApplication.instance())
self.tray.show()
self.sound = QSound(":/alert.wav")
def notify(self, message):
self.tray.showMessage("提醒", message, QSystemTrayIcon.Information, 5000)
self.sound.play()
小细节:
QSound
播放短音频时延迟很低,几乎无感;如果想兼容 macOS/Linux,可考虑subprocess
调用afplay
或aplay
。
九、系统托盘 SystemTray
除了主界面最小化,还想右键菜单“退出”、“显示”更灵活。于是封装了 SystemTray
:
# system_tray.py
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction
from PyQt5.QtGui import QIcon
class SystemTray(QSystemTrayIcon):
def __init__(self, window):
super().__init__(QIcon(":/icons/tray.png"), window)
self.window = window
menu = QMenu()
show_act = QAction("显示主窗口", window)
exit_act = QAction("退出", window)
menu.addAction(show_act)
menu.addAction(exit_act)
self.setContextMenu(menu)
show_act.triggered.connect(self._show_window)
exit_act.triggered.connect(window.close)
self.activated.connect(self._on_activated)
def _show_window(self):
self.window.show()
def _on_activated(self, reason):
if reason == QSystemTrayIcon.DoubleClick:
self._show_window()
这样,托盘图标右键能唤出菜单,双击还能快速恢复。
十、界面美化与 QSS
原生窗口有点单调,我在 resources/style.qss
写了样式表:
QMainWindow {
background: #ffffff;
}
QLabel {
font-size: 32px;
color: #333333;
}
QPushButton {
border: none;
padding: 5px;
border-radius: 4px;
}
QPushButton:hover {
background: #f0f0f0;
}
QListWidget {
border: 1px solid #ddd;
}
QTimeEdit, QLineEdit {
border: 1px solid #ccc;
border-radius: 4px;
padding: 2px 4px;
}
并在 main.py
中加载它:
# main.py
import sys
from PyQt5.QtWidgets import QApplication
from main_window import MainWindow
import resources_rc
if __name__ == "__main__":
app = QApplication(sys.argv)
with open("resources/style.qss", "r") as f:
app.setStyleSheet(f.read())
win = MainWindow()
win.show()
sys.exit(app.exec_())
十一、异常处理与容错
在开发过程中,我遇到过几次莫名其妙的崩溃:有时用户把电脑从睡眠唤醒后,QTimer
会突然停止;有时声音文件加载失败;有时 JSON 配置损坏。为此,我给以下关键区域加了 try-except:
ConfigManager.load()
:读取失败,重置为默认,并重写文件;TimerController._on_timeout()
:捕获所有异常,打印日志,保证定时器不会停;NotificationManager.notify()
:若通知或声音失败,至少要在控制台打印消息。
此外,还可以在主界面设一个“重置配置”按钮,一键清空 config.json
,恢复默认设置。
十二、打包与发布
代码完成后,想让朋友也能方便使用,于是用 PyInstaller 打包:
代码语言:bash复制pyinstaller --noconfirm --clean --windowed \
--name PomodoroTimer \
--add-data "resources/;resources/" \
main.py
打包选项说明与之前项目类似。生成的 dist/PomodoroTimer/
下直接分发,用户无需安装任何依赖。
biubiu~
十三、优化方向和扩展
项目基本达成最初设想,但后续还可以这样升级:
- 支持系统启动时自动最小化到托盘;
- 增加统计面板,记录完成的番茄钟次数;
- 利用
matplotlib
绘制专注时间折线图,可视化每日工作时长; - 支持在线同步配置,跨设备使用;
- 丰富提醒方式:文字转语音、桌面气泡、Email 推送。
每项功能都能让这个小工具更贴合个人需求,也能让我进一步钻研 PyQt 的更多特性。
总结
一路走来,从需求梳理、模块设计、编码实现,到美化、异常处理和打包,虽说只是一款小小的番茄钟,但它承载了我对 PyQt 功能的深度探索。更重要的是,每当定时器响起,告诉我该休息或该专注时,仿佛都在提醒我:合理规划时间,专注当前任务,效率必将提升。
如果你也想打造属于自己的定时提醒器,或想在此基础上加玩法,欢迎留言交流。希望我的开发历程,能给你一些启发和帮助。祝你高效 Coding,专注常在!
— END —
本文标签: PyQt5 番茄钟实现
版权声明:本文标题:PyQt5 番茄钟实现 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1747438154a2697157.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论