如何用Python案例实现托盘菜单:从零搭建系统托盘应用
目录导读
- 托盘菜单是什么?为什么用Python实现?
- 核心库介绍:pystray与PIL
- 基础案例:创建第一个系统托盘图标
- 进阶功能:动态菜单与右键交互
- 实战案例:带配置保存的计时器托盘工具
- 常见问题QA:图标不显示、菜单无响应等
- 性能与打包建议
托盘菜单是什么?为什么用Python实现?
系统托盘菜单(Tray Menu) 是驻留在操作系统任务栏通知区域(Windows托盘区/macOS菜单栏)的常驻程序图标,点击右键可弹出快捷菜单,用于执行后台任务、显示状态或快速启动功能。

Python之所以适合这类开发,因为它拥有轻量级的跨平台库 pystray,配合 Pillow 处理图标,只需几十行代码即可完成托盘程序,相比C#或C++,Python开发效率高且代码可读性强。
核心库介绍:pystray与PIL
1 pystray库
- 安装:
pip install pystray - 作用:创建系统托盘图标、绑定菜单事件、控制图标显示/隐藏。
- 核心类:
pystray.Icon、pystray.Menu、pystray.MenuItem
2 Pillow(PIL库)
- 安装:
pip install pillow - 作用:生成或修改图标图片,支持PNG、ICO、动态绘制。
- 核心方法:
Image.open()、Image.new()、ImageDraw
基础案例:创建第一个系统托盘图标
1 代码实现(含详细注释)
import pystray
from PIL import Image, ImageDraw
def create_image():
"""绘制一个64x64的蓝色圆形图标"""
image = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
draw.ellipse([10, 10, 54, 54], fill='blue', outline='white')
return image
def on_quit(icon, item):
"""退出菜单回调"""
icon.stop()
def on_show_info(icon, item):
"""弹出信息提示(实际可调用系统通知)"""
icon.notify("Hello! 托盘程序运行中", "提示")
# 创建菜单
menu = pystray.Menu(
pystray.MenuItem("显示信息", on_show_info),
pystray.MenuItem("退出", on_quit)
)
# 创建图标
icon = pystray.Icon("demo_tray", create_image(), "我的托盘", menu)
icon.run()
运行结果:通知区出现蓝色圆形图标,右键弹出“显示信息”和“退出”选项。
2 关键点解释
icon.run()启动事件循环,阻止主线程退出。MenuItem的default=True可设为双击默认执行项。
进阶功能:动态菜单与右键交互
1 动态更新菜单文本
适用场景:实时显示网络状态、CPU占用。
status = "断开"
def get_menu():
return pystray.Menu(
pystray.MenuItem(f"状态:{status}", None, enabled=False),
pystray.MenuItem("刷新状态", on_refresh),
pystray.MenuItem("退出", on_quit)
)
def on_refresh(icon, item):
global status
status = "已连接"
icon.menu = get_menu() # 重新设置菜单
icon.update_menu() # 立即刷新
注意:
icon.update_menu()确保菜单显示最新状态。
2 子菜单与复选框
menu = pystray.Menu(
pystray.MenuItem("设置", pystray.Menu(
pystray.MenuItem("自动启动", on_auto_start, checked=lambda item: auto_start),
pystray.MenuItem("☰ 高级选项", pystray.Menu(
pystray.MenuItem("清空日志", on_clear_log)
))
)),
pystray.MenuItem("退出", on_quit)
)
checked参数传入返回True/False的函数,控制复选框状态。
实战案例:带配置保存的计时器托盘工具
1 功能需求
- 托盘图标显示“开始计时”/“停止计时”菜单。
- 计时过程中,图标可显示剩余时间(或动态动画)。
- 退出时保存当前计时状态配置文件。
2 完整代码
import pystray
import threading
import time
import json
from PIL import Image, ImageDraw, ImageFont
class TimerTray:
def __init__(self):
self.running = False
self.start_time = None
self.duration = 5 # 秒,可配置
self.config_file = "timer_config.json"
self.load_config()
def load_config(self):
try:
with open(self.config_file, 'r') as f:
data = json.load(f)
self.duration = data.get('duration', 5)
except:
self.duration = 5
def save_config(self):
with open(self.config_file, 'w') as f:
json.dump({'duration': self.duration}, f)
def draw_timer_icon(self, elapsed):
"""绘制圆形进度图标"""
img = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# 画背景圆
draw.ellipse([4, 4, 60, 60], outline='gray', width=2)
# 画进度弧 (用90度起始)
if self.duration > 0:
angle = (elapsed / self.duration) * 360
draw.pieslice([4, 4, 60, 60], start=90, end=90+angle, fill='green')
# 中间显示秒数
font = ImageFont.load_default()
draw.text((20, 22), str(int(elapsed)), fill='white', font=font)
return img
def timer_thread(self, icon):
self.running = True
self.start_time = time.time()
while self.running and (time.time() - self.start_time) < self.duration:
elapsed = time.time() - self.start_time
icon.icon = self.draw_timer_icon(elapsed)
icon.update_menu()
time.sleep(0.5)
# 计时结束
self.running = False
icon.icon = self.draw_timer_icon(self.duration)
icon.notify("计时完成!", "提示")
# 重置图标为默认
icon.icon = Image.new('RGBA', (64,64), (0,150,0,255))
def on_start_stop(self, icon, item):
if self.running:
self.running = False
else:
# 新线程执行计时
threading.Thread(target=self.timer_thread, args=(icon,), daemon=True).start()
def run(self):
menu = pystray.Menu(
pystray.MenuItem(lambda: "停止计时" if self.running else "开始计时",
self.on_start_stop),
pystray.MenuItem("设置时长(当前{}秒)".format(self.duration),
lambda i, i2: setattr(self, 'duration', 10) and self.save_config()),
pystray.MenuItem("退出", lambda i, i2: (self.save_config(), i.stop()))
)
# 初始图标
img = Image.new('RGBA', (64,64), (0,150,0,255))
icon = pystray.Icon("timer_tray", img, "计时器", menu)
icon.run()
if __name__ == "__main__":
TimerTray().run()
3 运行效果
- 右键菜单动态显示“开始/停止”。
- 计时时图标秒数实时更新。
- 退出自动保存配置。
常见问题QA:图标不显示、菜单无响应等
Q1: 托盘图标直接消失,不显示?
A: 检查是否在非主线程创建了图标。pystray.run() 必须从主线程调用,且不能阻塞于其他线程,若需要多线程,使用 icon.run() 后主线程保持存活。
Q2: 右键点击菜单没反应?
A: 确认菜单回调函数签名正确:def func(icon, item):,缺失参数会导致静默失败,Windows系统下托盘图标右键菜单可能需要等待事件循环启动。
Q3: 图标显示缓慢或卡顿?
A: 避免菜单回调中执行耗时操作(如网络请求),可改为启动新线程处理,并调用 icon.update_menu() 或 icon.notify()。
Q4: macOS下图标风格不一致?
A: macOS的菜单栏图标尺寸固定为22x22像素,建议提供两套图标,或使用 pystray 的 template 参数适配系统主题。
Q5: 如何设置程序开机自启?
A: 在Windows中创建快捷方式到启动文件夹:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
性能与打包建议
- 性能优化:图标动态更新时,不要每帧生成新Image对象,可用
ImageDraw.Draw直接在原图绘制,或预生成不同状态图片缓存。 - 打包为exe:推荐使用
PyInstaller:pip install pyinstaller pyinstaller --onefile --windowed --add-data "icon.png;." your_script.py
--windowed避免显示控制台窗口,若图标不显示,需将图片资源打包进程序。 - 跨平台测试:Linux需要
python3-xlib或gtk支持,实测可能部分桌面环境不兼容。
通过本文的Python案例,你已经学会了使用 pystray + Pillow 从零搭建托盘菜单应用,从静态图标、动态菜单到带配置保存的计时器工具,每个案例都紧扣实际开发需求,无论你是想制作后台服务管理器、系统监控工具,还是快捷启动器,这些技术都能为你提供坚实的基础。
扩展阅读:
- 官方文档:
pystray.readthedocs.io - 图标字体库:
material-icons替换图标绘制