核心代码
import os
import win32api
import win32con
import win32gui
import win32gui_struct
class SysTrayIcon(object):
"""SysTrayIcon类用于显示任务栏图标"""
QUIT = 'QUIT'
SPECIAL_ACTIONS = [QUIT]
FIRST_ID = 5320
def __init__(s, icon, hover_text, menu_options, on_quit, tk_window=None, default_menu_index=None,
window_class_name=None):
"""
icon 需要显示的图标文件路径
hover_text 鼠标停留在图标上方时显示的文字
menu_options 右键菜单,格式: (('a', None, callback), ('b', None, (('b1', None, callback),)))
on_quit 传递退出函数,在执行退出时一并运行
tk_window 传递Tk窗口,s.root,用于单击图标显示窗口
default_menu_index 不显示的右键菜单序号
window_class_name 窗口类名
"""
s.icon = icon
s.hover_text = hover_text
s.on_quit = on_quit
s.root = tk_window
menu_options = menu_options + (('退出', None, s.QUIT),)
s._next_action_id = s.FIRST_ID
s.menu_actions_by_id = set()
s.menu_options = s._add_ids_to_menu_options(list(menu_options))
s.menu_actions_by_id = dict(s.menu_actions_by_id)
del s._next_action_id
s.default_menu_index = (default_menu_index or 0)
s.window_class_name = window_class_name or "SysTrayIconPy"
message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): s.restart,
win32con.WM_DESTROY: s.destroy,
win32con.WM_COMMAND: s.command,
win32con.WM_USER + 20: s.notify, }
# 注册窗口类。
wc = win32gui.WNDCLASS()
wc.hInstance = win32gui.GetModuleHandle(None)
wc.lpszClassName = s.window_class_name
wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
wc.hbrBackground = win32con.COLOR_WINDOW
wc.lpfnWndProc = message_map # 也可以指定wndproc.
s.classAtom = win32gui.RegisterClass(wc)
def restart(s, hwnd, msg, wparam, lparam):
s.refresh()
def activation(s):
"""激活任务栏图标,不用每次都重新创建新的托盘图标"""
hinst = win32gui.GetModuleHandle(None) # 创建窗口。
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
s.hwnd = win32gui.CreateWindow(s.classAtom,
s.window_class_name,
style,
0, 0,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
0, 0, hinst, None)
win32gui.UpdateWindow(s.hwnd)
s.notify_id = None
s.refresh_icon()
win32gui.PumpMessages()
def refresh(s, title='', msg='', time=500):
"""刷新托盘图标
title 标题
msg 内容,为空的话就不显示提示
time 提示显示时间"""
hinst = win32gui.GetModuleHandle(None)
if os.path.isfile(s.icon):
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
hicon = win32gui.LoadImage(hinst, s.icon, win32con.IMAGE_ICON,
0, 0, icon_flags)
else: # 找不到图标文件 - 使用默认值
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
if s.notify_id:
message = win32gui.NIM_MODIFY
else:
message = win32gui.NIM_ADD
s.notify_id = (s.hwnd, 0, # 句柄、托盘图标ID
win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP | win32gui.NIF_INFO,
# 托盘图标可以使用的功能的标识
win32con.WM_USER + 20, hicon,
s.hover_text, # 回调消息ID、托盘图标句柄、图标字符串
msg,
time,
title, # 提示内容、提示显示时间、提示标题
win32gui.NIIF_INFO # 提示用到的图标
)
win32gui.Shell_NotifyIcon(message, s.notify_id)
def refresh_icon(s, **data):
hinst = win32gui.GetModuleHandle(None)
if os.path.isfile(s.icon): # 尝试找到自定义图标
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
hicon = win32gui.LoadImage(hinst,
s.icon,
win32con.IMAGE_ICON,
0,
0,
icon_flags)
else: # 找不到图标文件 - 使用默认值
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
if s.notify_id:
message = win32gui.NIM_MODIFY
else:
message = win32gui.NIM_ADD
s.notify_id = (s.hwnd,
0,
win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
win32con.WM_USER + 20,
hicon,
s.hover_text)
win32gui.Shell_NotifyIcon(message, s.notify_id)
def show_menu(s):
"""显示右键菜单"""
menu = win32gui.CreatePopupMenu()
s.create_menu(menu, s.menu_options)
pos = win32gui.GetCursorPos()
win32gui.SetForegroundWindow(s.hwnd)
win32gui.TrackPopupMenu(menu,
win32con.TPM_LEFTALIGN,
pos[0],
pos[1],
0,
s.hwnd,
None)
win32gui.PostMessage(s.hwnd, win32con.WM_NULL, 0, 0)
def show_window(self):
self.root.deiconify() # 显示tk窗口
def _add_ids_to_menu_options(s, menu_options):
result = []
for menu_option in menu_options:
option_text, option_icon, option_action = menu_option
if callable(option_action) or option_action in s.SPECIAL_ACTIONS:
s.menu_actions_by_id.add((s._next_action_id, option_action))
result.append(menu_option + (s._next_action_id,))
else:
result.append((option_text,
option_icon,
s._add_ids_to_menu_options(option_action),
s._next_action_id))
s._next_action_id += 1
return result
def destroy(s, hwnd=None, msg=None, wparam=None, lparam=None, exit=1):
nid = (s.hwnd, 0)
win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
win32gui.PostQuitMessage(0) # 退出托盘图标
if s.on_quit:
s.on_quit() # 运行传递的on_quit
def notify(s, hwnd, msg, wparam, lparam):
'''鼠标事件'''
"""
可能的鼠标事件:
WM_MOUSEMOVE #光标经过图标
WM_LBUTTONDOWN #左键按下
WM_LBUTTONUP #左键弹起
WM_LBUTTONDBLCLK #双击左键
WM_RBUTTONDOWN #右键按下
WM_RBUTTONUP #右键弹起
WM_RBUTTONDBLCLK #双击右键
WM_MBUTTONDOWN #滚轮按下
WM_MBUTTONUP #滚轮弹起
WM_MBUTTONDBLCLK #双击滚轮
"""
if lparam == win32con.WM_LBUTTONDBLCLK: # 双击左键
pass
elif lparam == win32con.WM_RBUTTONUP: # 右键弹起
s.show_menu()
elif lparam == win32con.WM_LBUTTONUP: # 左键弹起
s.show_window()
return True
def create_menu(s, menu, menu_options):
for option_text, option_icon, option_action, option_id in menu_options[::-1]:
if option_icon:
option_icon = s.prep_menu_icon(option_icon)
if option_id in s.menu_actions_by_id:
item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
hbmpItem=option_icon,
wID=option_id)
win32gui.InsertMenuItem(menu, 0, 1, item)
else:
submenu = win32gui.CreatePopupMenu()
s.create_menu(submenu, option_action)
item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
hbmpItem=option_icon,
hSubMenu=submenu)
win32gui.InsertMenuItem(menu, 0, 1, item)
def prep_menu_icon(s, icon):
# 加载图标。
ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)
hdcBitmap = win32gui.CreateCompatibleDC(0)
hdcScreen = win32gui.GetDC(0)
hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y)
hbmOld = win32gui.SelectObject(hdcBitmap, hbm)
brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)
win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush)
win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)
win32gui.SelectObject(hdcBitmap, hbmOld)
win32gui.DeleteDC(hdcBitmap)
return hbm
def command(s, hwnd, msg, wparam, lparam):
id = win32gui.LOWORD(wparam)
s.execute_menu_option(id)
def execute_menu_option(s, id):
menu_action = s.menu_actions_by_id[id]
if menu_action == s.QUIT:
print("ss")
win32gui.DestroyWindow(s.hwnd)
else:
menu_action(s)
使用方法
# 重现关闭按钮事件
self.root.protocol("WM_DELETE_WINDOW", self.close_)
# 开启常驻后台线程
self.backend_thread = threading.Thread(target=self.backend)
self.backend_thread.setDaemon(True)
self.backend_thread.start()
def backend(self, hover_text=None):
"""
后台图标线程
"""
if hover_text is None:
hover_text = self.root.title()
menu_options = (('主页', None, self.show),)
self.SysTrayIcon = SysTrayIcon(
icon=icon_ico, # 图标
hover_text=hover_text, # 光标停留显示文字
menu_options=menu_options, # 右键菜单
on_quit=self.exit, # 退出调用
tk_window=self.root, # Tk窗口
)
self.SysTrayIcon.activation()
def show(self):
self.root.deiconify()
def exit(self):
self.root.destroy()
def close_(self):
self.root.withdraw()