核心代码

  1. import os
  2. import win32api
  3. import win32con
  4. import win32gui
  5. import win32gui_struct
  6. class SysTrayIcon(object):
  7. """SysTrayIcon类用于显示任务栏图标"""
  8. QUIT = 'QUIT'
  9. SPECIAL_ACTIONS = [QUIT]
  10. FIRST_ID = 5320
  11. def __init__(s, icon, hover_text, menu_options, on_quit, tk_window=None, default_menu_index=None,
  12. window_class_name=None):
  13. """
  14. icon 需要显示的图标文件路径
  15. hover_text 鼠标停留在图标上方时显示的文字
  16. menu_options 右键菜单,格式: (('a', None, callback), ('b', None, (('b1', None, callback),)))
  17. on_quit 传递退出函数,在执行退出时一并运行
  18. tk_window 传递Tk窗口,s.root,用于单击图标显示窗口
  19. default_menu_index 不显示的右键菜单序号
  20. window_class_name 窗口类名
  21. """
  22. s.icon = icon
  23. s.hover_text = hover_text
  24. s.on_quit = on_quit
  25. s.root = tk_window
  26. menu_options = menu_options + (('退出', None, s.QUIT),)
  27. s._next_action_id = s.FIRST_ID
  28. s.menu_actions_by_id = set()
  29. s.menu_options = s._add_ids_to_menu_options(list(menu_options))
  30. s.menu_actions_by_id = dict(s.menu_actions_by_id)
  31. del s._next_action_id
  32. s.default_menu_index = (default_menu_index or 0)
  33. s.window_class_name = window_class_name or "SysTrayIconPy"
  34. message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): s.restart,
  35. win32con.WM_DESTROY: s.destroy,
  36. win32con.WM_COMMAND: s.command,
  37. win32con.WM_USER + 20: s.notify, }
  38. # 注册窗口类。
  39. wc = win32gui.WNDCLASS()
  40. wc.hInstance = win32gui.GetModuleHandle(None)
  41. wc.lpszClassName = s.window_class_name
  42. wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
  43. wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
  44. wc.hbrBackground = win32con.COLOR_WINDOW
  45. wc.lpfnWndProc = message_map # 也可以指定wndproc.
  46. s.classAtom = win32gui.RegisterClass(wc)
  47. def restart(s, hwnd, msg, wparam, lparam):
  48. s.refresh()
  49. def activation(s):
  50. """激活任务栏图标,不用每次都重新创建新的托盘图标"""
  51. hinst = win32gui.GetModuleHandle(None) # 创建窗口。
  52. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
  53. s.hwnd = win32gui.CreateWindow(s.classAtom,
  54. s.window_class_name,
  55. style,
  56. 0, 0,
  57. win32con.CW_USEDEFAULT,
  58. win32con.CW_USEDEFAULT,
  59. 0, 0, hinst, None)
  60. win32gui.UpdateWindow(s.hwnd)
  61. s.notify_id = None
  62. s.refresh_icon()
  63. win32gui.PumpMessages()
  64. def refresh(s, title='', msg='', time=500):
  65. """刷新托盘图标
  66. title 标题
  67. msg 内容,为空的话就不显示提示
  68. time 提示显示时间"""
  69. hinst = win32gui.GetModuleHandle(None)
  70. if os.path.isfile(s.icon):
  71. icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
  72. hicon = win32gui.LoadImage(hinst, s.icon, win32con.IMAGE_ICON,
  73. 0, 0, icon_flags)
  74. else: # 找不到图标文件 - 使用默认值
  75. hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
  76. if s.notify_id:
  77. message = win32gui.NIM_MODIFY
  78. else:
  79. message = win32gui.NIM_ADD
  80. s.notify_id = (s.hwnd, 0, # 句柄、托盘图标ID
  81. win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP | win32gui.NIF_INFO,
  82. # 托盘图标可以使用的功能的标识
  83. win32con.WM_USER + 20, hicon,
  84. s.hover_text, # 回调消息ID、托盘图标句柄、图标字符串
  85. msg,
  86. time,
  87. title, # 提示内容、提示显示时间、提示标题
  88. win32gui.NIIF_INFO # 提示用到的图标
  89. )
  90. win32gui.Shell_NotifyIcon(message, s.notify_id)
  91. def refresh_icon(s, **data):
  92. hinst = win32gui.GetModuleHandle(None)
  93. if os.path.isfile(s.icon): # 尝试找到自定义图标
  94. icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
  95. hicon = win32gui.LoadImage(hinst,
  96. s.icon,
  97. win32con.IMAGE_ICON,
  98. 0,
  99. 0,
  100. icon_flags)
  101. else: # 找不到图标文件 - 使用默认值
  102. hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
  103. if s.notify_id:
  104. message = win32gui.NIM_MODIFY
  105. else:
  106. message = win32gui.NIM_ADD
  107. s.notify_id = (s.hwnd,
  108. 0,
  109. win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
  110. win32con.WM_USER + 20,
  111. hicon,
  112. s.hover_text)
  113. win32gui.Shell_NotifyIcon(message, s.notify_id)
  114. def show_menu(s):
  115. """显示右键菜单"""
  116. menu = win32gui.CreatePopupMenu()
  117. s.create_menu(menu, s.menu_options)
  118. pos = win32gui.GetCursorPos()
  119. win32gui.SetForegroundWindow(s.hwnd)
  120. win32gui.TrackPopupMenu(menu,
  121. win32con.TPM_LEFTALIGN,
  122. pos[0],
  123. pos[1],
  124. 0,
  125. s.hwnd,
  126. None)
  127. win32gui.PostMessage(s.hwnd, win32con.WM_NULL, 0, 0)
  128. def show_window(self):
  129. self.root.deiconify() # 显示tk窗口
  130. def _add_ids_to_menu_options(s, menu_options):
  131. result = []
  132. for menu_option in menu_options:
  133. option_text, option_icon, option_action = menu_option
  134. if callable(option_action) or option_action in s.SPECIAL_ACTIONS:
  135. s.menu_actions_by_id.add((s._next_action_id, option_action))
  136. result.append(menu_option + (s._next_action_id,))
  137. else:
  138. result.append((option_text,
  139. option_icon,
  140. s._add_ids_to_menu_options(option_action),
  141. s._next_action_id))
  142. s._next_action_id += 1
  143. return result
  144. def destroy(s, hwnd=None, msg=None, wparam=None, lparam=None, exit=1):
  145. nid = (s.hwnd, 0)
  146. win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
  147. win32gui.PostQuitMessage(0) # 退出托盘图标
  148. if s.on_quit:
  149. s.on_quit() # 运行传递的on_quit
  150. def notify(s, hwnd, msg, wparam, lparam):
  151. '''鼠标事件'''
  152. """
  153. 可能的鼠标事件:
  154. WM_MOUSEMOVE #光标经过图标
  155. WM_LBUTTONDOWN #左键按下
  156. WM_LBUTTONUP #左键弹起
  157. WM_LBUTTONDBLCLK #双击左键
  158. WM_RBUTTONDOWN #右键按下
  159. WM_RBUTTONUP #右键弹起
  160. WM_RBUTTONDBLCLK #双击右键
  161. WM_MBUTTONDOWN #滚轮按下
  162. WM_MBUTTONUP #滚轮弹起
  163. WM_MBUTTONDBLCLK #双击滚轮
  164. """
  165. if lparam == win32con.WM_LBUTTONDBLCLK: # 双击左键
  166. pass
  167. elif lparam == win32con.WM_RBUTTONUP: # 右键弹起
  168. s.show_menu()
  169. elif lparam == win32con.WM_LBUTTONUP: # 左键弹起
  170. s.show_window()
  171. return True
  172. def create_menu(s, menu, menu_options):
  173. for option_text, option_icon, option_action, option_id in menu_options[::-1]:
  174. if option_icon:
  175. option_icon = s.prep_menu_icon(option_icon)
  176. if option_id in s.menu_actions_by_id:
  177. item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
  178. hbmpItem=option_icon,
  179. wID=option_id)
  180. win32gui.InsertMenuItem(menu, 0, 1, item)
  181. else:
  182. submenu = win32gui.CreatePopupMenu()
  183. s.create_menu(submenu, option_action)
  184. item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
  185. hbmpItem=option_icon,
  186. hSubMenu=submenu)
  187. win32gui.InsertMenuItem(menu, 0, 1, item)
  188. def prep_menu_icon(s, icon):
  189. # 加载图标。
  190. ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
  191. ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
  192. hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)
  193. hdcBitmap = win32gui.CreateCompatibleDC(0)
  194. hdcScreen = win32gui.GetDC(0)
  195. hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y)
  196. hbmOld = win32gui.SelectObject(hdcBitmap, hbm)
  197. brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)
  198. win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush)
  199. win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)
  200. win32gui.SelectObject(hdcBitmap, hbmOld)
  201. win32gui.DeleteDC(hdcBitmap)
  202. return hbm
  203. def command(s, hwnd, msg, wparam, lparam):
  204. id = win32gui.LOWORD(wparam)
  205. s.execute_menu_option(id)
  206. def execute_menu_option(s, id):
  207. menu_action = s.menu_actions_by_id[id]
  208. if menu_action == s.QUIT:
  209. print("ss")
  210. win32gui.DestroyWindow(s.hwnd)
  211. else:
  212. menu_action(s)

使用方法

  1. # 重现关闭按钮事件
  2. self.root.protocol("WM_DELETE_WINDOW", self.close_)
  3. # 开启常驻后台线程
  4. self.backend_thread = threading.Thread(target=self.backend)
  5. self.backend_thread.setDaemon(True)
  6. self.backend_thread.start()
  7. def backend(self, hover_text=None):
  8. """
  9. 后台图标线程
  10. """
  11. if hover_text is None:
  12. hover_text = self.root.title()
  13. menu_options = (('主页', None, self.show),)
  14. self.SysTrayIcon = SysTrayIcon(
  15. icon=icon_ico, # 图标
  16. hover_text=hover_text, # 光标停留显示文字
  17. menu_options=menu_options, # 右键菜单
  18. on_quit=self.exit, # 退出调用
  19. tk_window=self.root, # Tk窗口
  20. )
  21. self.SysTrayIcon.activation()
  22. def show(self):
  23. self.root.deiconify()
  24. def exit(self):
  25. self.root.destroy()
  26. def close_(self):
  27. self.root.withdraw()