如何用Python案例实现托盘菜单?

wen python案例 3

如何用Python案例实现托盘菜单:从零搭建系统托盘应用

目录导读

  1. 托盘菜单是什么?为什么用Python实现?
  2. 核心库介绍:pystray与PIL
  3. 基础案例:创建第一个系统托盘图标
  4. 进阶功能:动态菜单与右键交互
  5. 实战案例:带配置保存的计时器托盘工具
  6. 常见问题QA:图标不显示、菜单无响应等
  7. 性能与打包建议

托盘菜单是什么?为什么用Python实现?

系统托盘菜单(Tray Menu) 是驻留在操作系统任务栏通知区域(Windows托盘区/macOS菜单栏)的常驻程序图标,点击右键可弹出快捷菜单,用于执行后台任务、显示状态或快速启动功能。

如何用Python案例实现托盘菜单?

Python之所以适合这类开发,因为它拥有轻量级的跨平台库 pystray,配合 Pillow 处理图标,只需几十行代码即可完成托盘程序,相比C#或C++,Python开发效率高且代码可读性强。


核心库介绍:pystray与PIL

1 pystray库

  • 安装pip install pystray
  • 作用:创建系统托盘图标、绑定菜单事件、控制图标显示/隐藏。
  • 核心类pystray.Iconpystray.Menupystray.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() 启动事件循环,阻止主线程退出。
  • MenuItemdefault=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像素,建议提供两套图标,或使用 pystraytemplate 参数适配系统主题。

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-xlibgtk 支持,实测可能部分桌面环境不兼容。

通过本文的Python案例,你已经学会了使用 pystray + Pillow 从零搭建托盘菜单应用,从静态图标、动态菜单到带配置保存的计时器工具,每个案例都紧扣实际开发需求,无论你是想制作后台服务管理器、系统监控工具,还是快捷启动器,这些技术都能为你提供坚实的基础。

扩展阅读

  • 官方文档:pystray.readthedocs.io
  • 图标字体库:material-icons 替换图标绘制

抱歉,评论功能暂时关闭!