核心代码
import osimport win32apiimport win32conimport win32guiimport win32gui_structclass 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()