太棒了!下面给你两种 可直接复制运行 的 Python 实现:一个是功能更强、外观更现代的 PyQt5(推荐) 版本;另一个是更轻量、仅依赖标准库的 Tkinter 版本,适合不想装额外库的场景。两个都做了自定义样式、自动消失与交互按钮,方便你直接拿来当桌面“温馨提醒”弹窗。
方案 A — PyQt5(更美观、动画流畅,推荐)
特点:圆角、半透明、阴影、滑入/淡出动画、可自定义图标与按钮
先安装依赖:
pip install PyQt5 pillow
把下面代码保存为 toast_pyqt.py,运行 python toast_pyqt.py。
# toast_pyqt.py
"""
美观桌面提醒(PyQt5)
- 圆角、半透明、阴影
- 滑入与淡出动画
- 支持图标、标题、内容、持续时间
"""
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PIL import Image, ImageQt
import os
class Toast(QtWidgets.QWidget):
def __init__(self, title, message, duration=4000, icon_path=None, parent=None):
super().__init__(parent, flags=QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
self.duration = duration
self.opacity_effect = QtWidgets.QGraphicsOpacityEffect(self)
self.setGraphicsEffect(self.opacity_effect)
self.opacity_effect.setOpacity(0.0)
# Window flags for click-through? (we keep it clickable)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setWindowFlag(QtCore.Qt.Tool) # don't show in taskbar
# Layout
layout = QtWidgets.QHBoxLayout()
layout.setContentsMargins(14, 12, 14, 12)
layout.setSpacing(12)
# Icon
if icon_path and os.path.exists(icon_path):
pix = QtGui.QPixmap(icon_path).scaled(48, 48, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
icon_label = QtWidgets.QLabel()
icon_label.setPixmap(pix)
layout.addWidget(icon_label)
else:
layout.addSpacing(0)
# Texts
text_layout = QtWidgets.QVBoxLayout()
self.title_label = QtWidgets.QLabel(title)
self.title_label.setStyleSheet("font-weight:600; font-size:14px; color: #222;")
self.message_label = QtWidgets.QLabel(message)
self.message_label.setStyleSheet("font-size:12px; color: #333;")
self.message_label.setWordWrap(True)
text_layout.addWidget(self.title_label)
text_layout.addWidget(self.message_label)
layout.addLayout(text_layout)
# Close button
close_btn = QtWidgets.QPushButton("✕")
close_btn.setFixedSize(24, 24)
close_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
close_btn.setStyleSheet("""
QPushButton{
border: none;
background: transparent;
font-size:12px;
}
QPushButton:hover{ color: #f00; }
""")
close_btn.clicked.connect(self.close_with_animation)
layout.addWidget(close_btn, alignment=QtCore.Qt.AlignTop)
# Container frame to allow rounded corners + background
frame = QtWidgets.QFrame()
frame.setLayout(layout)
frame.setObjectName("frame")
frame.setStyleSheet("""
QFrame#frame {
background: qlineargradient(x1:0 y1:0, x2:1 y2:1,
stop:0 rgba(255,255,255,240), stop:1 rgba(245,245,245,230));
border-radius: 12px;
}
""")
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(8,8,8,8)
main_layout.addWidget(frame)
# Drop shadow
shadow = QtWidgets.QGraphicsDropShadowEffect(self)
shadow.setBlurRadius(18)
shadow.setOffset(0, 6)
shadow.setColor(QtGui.QColor(0,0,0,120))
frame.setGraphicsEffect(shadow)
# Size hint
self.resize(360, 90)
# Timers & animations
self.show_anim = QtCore.QPropertyAnimation(self.opacity_effect, b"opacity")
self.show_anim.setDuration(350)
self.show_anim.setStartValue(0.0)
self.show_anim.setEndValue(1.0)
self.hide_anim = QtCore.QPropertyAnimation(self.opacity_effect, b"opacity")
self.hide_anim.setDuration(350)
self.hide_anim.setStartValue(1.0)
self.hide_anim.setEndValue(0.0)
self.hide_anim.finished.connect(super().close)
self._auto_close_timer = QtCore.QTimer(self)
self._auto_close_timer.setSingleShot(True)
self._auto_close_timer.timeout.connect(self.close_with_animation)
def show(self):
super().show()
self.raise_()
self.show_anim.start()
self._auto_close_timer.start(self.duration)
def close_with_animation(self):
if self.hide_anim.state() == QtCore.QAbstractAnimation.Running:
return
self.hide_anim.start()
def show_toast(title, message, duration=4000, icon_path=None):
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
toast = Toast(title, message, duration=duration, icon_path=icon_path)
# Place at bottom-right of primary screen (or top-right if preferred)
screen_geo = QtWidgets.QApplication.primaryScreen().availableGeometry()
x = screen_geo.x() + screen_geo.width() - toast.width() - 20
y = screen_geo.y() + screen_geo.height() - toast.height() - 40
toast.move(x, y)
toast.show()
# If we created the app, exec
if not QtWidgets.QApplication.instance():
sys.exit(app.exec_())
if __name__ == "__main__":
# 示例调用
# 准备一个小图标(可选)
icon_file = None
# icon_file = "reminder_icon.png" # 放一个 64x64 的 png 在同目录下试试
show_toast("温馨提醒", "今天下午 3:30 有团队例会,别忘了准备PPT哦~", duration=6000, icon_path=icon_file)
可自定义项(在调用 show_toast(...) 时传入)
title(标题)、message(主文案)、duration(毫秒)、icon_path(图标路径)。- 可改位置:把
y改小为20就会变成右上角弹出效果。
方案 B — Tkinter(轻量、跨平台但样式较基础)
不需要额外依赖,直接运行即可。保存为 toast_tkinter.py。
# toast_tkinter.py
"""
简单桌面提醒(Tkinter)
- 纯 Python 标准库
- 圆角/阴影受限,但跨平台且无需安装第三方包
"""
import tkinter as tk
import threading
import time
class Toast(tk.Toplevel):
def __init__(self, title, message, duration=4, **kwargs):
super().__init__(**kwargs)
self.overrideredirect(True) # 去掉窗口边框
self.attributes("-topmost", True)
self.config(bg="#333333")
self.duration = duration
frm = tk.Frame(self, bg="#ffffff", bd=0)
frm.pack(padx=6, pady=6)
title_lbl = tk.Label(frm, text=title, font=("Helvetica", 12, "bold"), bg="#ffffff")
title_lbl.pack(anchor="w")
msg_lbl = tk.Label(frm, text=message, font=("Helvetica", 10), bg="#ffffff", wraplength=320, justify="left")
msg_lbl.pack(anchor="w", pady=(4,0))
btn = tk.Button(frm, text="关闭", command=self.close)
btn.pack(anchor="e", pady=(8,0))
self.update_idletasks()
w = self.winfo_width()
h = self.winfo_height()
# 右下角
screen_w = self.winfo_screenwidth()
screen_h = self.winfo_screenheight()
x = screen_w - w - 20
y = screen_h - h - 50
self.geometry(f"{w}x{h}+{x}+{y}")
# 自动关闭线程
threading.Thread(target=self._auto_close, daemon=True).start()
def _auto_close(self):
time.sleep(self.duration)
try:
self.close()
except:
pass
def close(self):
self.destroy()
if __name__ == "__main__":
root = tk.Tk()
root.withdraw()
Toast("温馨提醒", "别忘了 18:00 去健身房运动~", duration=5)
root.mainloop()
使用建议 & 小贴士
- 如果追求“原生感”与更多动画,优先用 PyQt5 / PySide6。两者 API 类似,若用 PySide6,只需把
PyQt5的 import 换成PySide6相应模块(注意小差异)。 - 想要系统通知(不保证可自定义样式)可以考虑
plyer/win10toast(Windows)/notify-send(Linux)/osascript(macOS)。但这些通常样式受限,不如自定义窗口灵活。 - 可扩展功能:支持「点击跳转打开某个 URL / 程序」、「带进度条的倒计时」、「多个同时显示(管理堆叠)」、「调度定时提醒(结合
schedule或系统定时任务)」。 - 打包成单个可执行文件:用
pyinstaller --noconfirm --onefile toast_pyqt.py(注意 PyQt 打包需要额外处理资源)。
如果你想,我可以:
- 把 PyQt 版本改成 右上角横幅、或支持多个同时排队显示;
- 加入“稍后提醒 10 分钟”的按钮并演示完整实现;
- 或把样式改成你喜欢的配色 / 动画风格(举例:模糊背景、渐变色、可拖动窗口)。
想怎么定制就告诉我(比如配色、字体、按钮样式或需要的交互),我直接把代码改好给你。
发表回复