As suggested in Can't Get A Menu to Work On Windows 10 #99 and pystray Icon in a thread will not end the thread with Icon.stop() #94 on official pystray Github: if using Windows or Linux, the run_detached() method is unnecessary, even if using alongside Tkinter or other GUIs that utilize its own mainloop (ignore if using or intend to deploy on macs).
Also, other methods seem cluttered or unnecessarily complicated...
For a clean, simple and extensible solution that you can implement universally in any program, see the below code; it uses a variable (self.active) to inform whether the tray icon is still running (ie. user hasn't exited the program via tray icon), one function for you to allocate MenuItems, and another function that creates the actual icon itself, implemented using Thread.threading module. It uses some of the code from the above linked Github issues.
For your easy testing purposes, I've also included pystray's function from issue #94 linked above that creates a icon for you. Of course, you may replace this with the path to your icon file, which would make this look cleaner and more succinct.
import threading
from PIL import Image, ImageDraw
from pystray import Icon, Menu, MenuItem
def create_image(color1, color2, width=64, height=64):
image = Image.new("RGB", (width, height), color1)
dc = ImageDraw.Draw(image)
dc.rectangle((width // 2, 0, width, height // 2), fill=color2)
dc.rectangle((0, height // 2, width // 2, height), fill=color2)
return image
class TrayIcon:
def __init__(self):
self.active = True
def _on_clicked(self, icon, item):
if str(item) == "Settings":
print("Opening Settings")
elif str(item) == "Open ReadMe":
print("Opening ReadMe")
elif str(item) == "Exit":
icon.visible = False
icon.stop()
self.active = False
def create_icon(self):
thread = threading.Thread(
daemon=True,
target=lambda: Icon(
"test",
create_image("black", "white"),
menu=Menu(
MenuItem("Settings", self._on_clicked),
MenuItem("Open ReadMe", self._on_clicked),
MenuItem("Exit", self._on_clicked),
),
).run(),
)
thread.start()
And then, all you need to do in your view or tkinter main file, is include this (don't forget to import your TrayIcon class first):
tray_icon = TrayIcon()
tray_icon.create_icon()
while tray_icon.active == True:
print("whatever you want to run here, put it here.")
print("including tkinter mainloops.")
Let me know if this helps you :)
tkinter
app withinpystray
? Search Q&A pystray – Sharonsharona