├── .gitignore ├── LICENSE ├── README.md ├── demo.py ├── ding.ico ├── favicon.ico ├── github.ico ├── python.ico ├── pywin10 └── __init__.py ├── setup.py ├── 主页.ico ├── 日历.ico ├── 等待文件.ico ├── 编辑.ico └── 退出.ico /.gitignore: -------------------------------------------------------------------------------- 1 | _doc 2 | *.egg 3 | *.egg-info 4 | dist 5 | build 6 | .idea 7 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 gaoyongxian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to pywin10 2 | 3 | ### 简介 4 | 5 | `pywin10`基于**pywin32**,封装了菜单,通知功能.可以搭配**tkinter**,方便使用. 6 | 7 | ### 安装 8 | 9 | pip install pywin10 10 | 11 | ### 效果 12 | 13 | ![预览图](https://pic.imgdb.cn/item/61a5c59b2ab3f51d91cf1f17.png) 14 | 15 | ![预览图](https://pic.imgdb.cn/item/61a5c59b2ab3f51d91cf1f1e.png) 16 | 17 | ![预览图](https://pic.imgdb.cn/item/61a5c59b2ab3f51d91cf1f23.png) 18 | 19 | ### 开始 20 | 21 | import threading 22 | import tkinter 23 | import win32gui 24 | from pywin10 import TaskBarIcon 25 | 26 | 27 | class MainWindow: 28 | def __init__(self): 29 | self.root = tkinter.Tk() 30 | 31 | # 开启常驻后台线程 32 | backend_thread = threading.Thread(target=self.backend) 33 | backend_thread.setDaemon(True) 34 | backend_thread.start() 35 | 36 | # 设置当点击窗体时弹出通知 37 | self.root.bind('', self._on_tap) 38 | # 自定义关闭按钮 39 | self.root.protocol("WM_DELETE_WINDOW", self._close) 40 | 41 | self.root.mainloop() 42 | 43 | def _on_tap(self, event): 44 | self.t.ShowToast() 45 | 46 | def _close(self): 47 | self.t.ShowToast(title="最小化", msg="窗口已经最小化到图标") 48 | self.root.withdraw() 49 | 50 | def _show(self): 51 | self.root.deiconify() 52 | 53 | def ding(self, *args): 54 | print("ding 接收参数:", args) 55 | 56 | def _left_click(self, *args): 57 | print("_left_click 接收参数:", args) 58 | 59 | def exit(self): 60 | # 退出 TaskBarIcon 61 | win32gui.DestroyWindow(self.t.hwnd) 62 | # 退出 Tkinter 63 | self.root.destroy() 64 | 65 | def backend(self): 66 | # TaskBarIcon 里面的参数全部都不是必须的,即便self.t = TaskBarIcon(),你一样可以发送通知等. 67 | self.t = TaskBarIcon( 68 | left_click=(self._left_click, (1, 2)), # 左键单击回调函数,可以不设置(如果想要传参,这样写(func,(arg1,arg2))) 69 | double_click=self._show, # 左键双击回调函数,可以不设置(如果不想传参,直接写函数名称) 70 | icon="python.ico", # 设置图标,可以不设置 71 | hover_text="TaskBarIcon", # 设置悬浮在小图标显示的文字,可以不设置 72 | menu_options=[ # 可以不设置 73 | ['退出', "退出.ico", self.exit, 1], # 菜单项格式:["菜单项名称","菜单项图标路径或None",回调函数或者子菜单列表,id数字(随便写不要重复即可)] 74 | ["分隔符", None, None, 111], 75 | ['顶一顶', "ding.ico", (self.ding, (1, 2, 3)), 44], 76 | ['日历', "日历.ico", None, 3], 77 | ['主页', "主页.ico", self._show, 2], 78 | ["分隔符", None, None, 7], 79 | ["更多选项", "编辑.ico", [ 80 | ['使用说明', "等待文件.ico", None, 25], 81 | ["分隔符", None, None, 17], 82 | ['hello', "github.ico", None, 16], 83 | ['hello2', "github.ico", None, 1116], 84 | ], 4], 85 | ], 86 | menu_style="iconic", # 设置右键菜单的模式,可以不设置:normal(不展示图标),iconic(展示图标) 87 | icon_x_pad=12, # 设置图标左边距 88 | ) 89 | # 注意这是死循环,类似与tkinter中的mainloop, 90 | # 因为都是死循环,所以与mainloop会冲突,放到线程里面执行. 91 | win32gui.PumpMessages() 92 | 93 | 94 | if __name__ == '__main__': 95 | MainWindow() 96 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @Project :pywin10 4 | @File :demo.py 5 | @Author :Gao yongxian 6 | @Date :2021/11/30 13:03 7 | @contact: g1695698547@163.com 8 | """ 9 | import threading 10 | import tkinter 11 | import win32gui 12 | from pywin10 import TaskBarIcon 13 | 14 | 15 | class MainWindow: 16 | def __init__(self): 17 | self.root = tkinter.Tk() 18 | 19 | # 开启常驻后台线程 20 | backend_thread = threading.Thread(target=self.backend) 21 | backend_thread.setDaemon(True) 22 | backend_thread.start() 23 | 24 | # 设置当点击窗体时弹出通知 25 | self.root.bind('', self._on_tap) 26 | # 自定义关闭按钮 27 | self.root.protocol("WM_DELETE_WINDOW", self._close) 28 | 29 | self.root.mainloop() 30 | 31 | def _on_tap(self, event): 32 | self.t.ShowToast() 33 | 34 | def _close(self): 35 | self.t.ShowToast(title="最小化", msg="窗口已经最小化到图标") 36 | self.root.withdraw() 37 | 38 | def _show(self): 39 | self.root.deiconify() 40 | 41 | def ding(self, *args): 42 | print("ding 接收参数:", args) 43 | 44 | def _left_click(self, *args): 45 | print("_left_click 接收参数:", args) 46 | 47 | def exit(self): 48 | # 退出 TaskBarIcon 49 | win32gui.DestroyWindow(self.t.hwnd) 50 | # 退出 Tkinter 51 | self.root.destroy() 52 | 53 | def backend(self): 54 | # TaskBarIcon 里面的参数全部都不是必须的,即便self.t = TaskBarIcon(),你一样可以发送通知等. 55 | self.t = TaskBarIcon( 56 | left_click=(self._left_click, (1, 2)), # 左键单击回调函数,可以不设置(如果想要传参,这样写(func,(arg1,arg2))) 57 | double_click=self._show, # 左键双击回调函数,可以不设置(如果不想传参,直接写函数名称) 58 | icon="python.ico", # 设置图标,可以不设置 59 | hover_text="TaskBarIcon", # 设置悬浮在小图标显示的文字,可以不设置 60 | menu_options=[ # 可以不设置 61 | ['退出', "退出.ico", self.exit, 1], # 菜单项格式:["菜单项名称","菜单项图标路径或None",回调函数或者子菜单列表,id数字(随便写不要重复即可)] 62 | ["分隔符", None, None, 111], 63 | ['顶一顶', "ding.ico", (self.ding, (1, 2, 3)), 44], 64 | ['日历', "日历.ico", None, 3], 65 | ['主页', "主页.ico", self._show, 2], 66 | ["分隔符", None, None, 7], 67 | ["更多选项", "编辑.ico", [ 68 | ['使用说明', "等待文件.ico", None, 25], 69 | ["分隔符", None, None, 17], 70 | ['hello', "github.ico", None, 16], 71 | ['hello2', "github.ico", None, 1116], 72 | ], 4], 73 | ], 74 | menu_style="iconic", # 设置右键菜单的模式,可以不设置:normal(不展示图标),iconic(展示图标) 75 | icon_x_pad=12, # 设置图标左边距 76 | ) 77 | # 注意这是死循环,类似与tkinter中的mainloop, 78 | # 因为都是死循环,所以与mainloop会冲突,放到线程里面执行. 79 | win32gui.PumpMessages() 80 | 81 | 82 | if __name__ == '__main__': 83 | MainWindow() 84 | -------------------------------------------------------------------------------- /ding.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/ding.ico -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/favicon.ico -------------------------------------------------------------------------------- /github.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/github.ico -------------------------------------------------------------------------------- /python.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/python.ico -------------------------------------------------------------------------------- /pywin10/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @Project :pywin10 4 | @File :__init__.py.py 5 | @Author :Gao yongxian 6 | @Date :2021/11/30 13:01 7 | @contact: g1695698547@163.com 8 | """ 9 | 10 | # 参考项目(Pywin32项目下有丰富的Demo):https://github.com/Travis-Sun/pywin32 11 | # Pywin32的在线文档:http://timgolden.me.uk/pywin32-docs/win32.html 12 | # Pywin32的离线文档:https://gaoyongxian.lanzoui.com/iNCGWx0p5ad 13 | 14 | # 当前仅封装 一级菜单 多级菜单 普通菜单项 带icon的菜单项 15 | # 如果想要实现 特殊菜单项(MFS_CHECKED(选择)、MFS_GRAYED(禁用)、MFT_RIGHTJUSTIFY(显示在右边)),请参考Pywin32项目下的demo文件,自行编写。 16 | 17 | import struct 18 | import threading 19 | import time 20 | import win32api 21 | import os 22 | import win32con 23 | from win32con import WM_USER 24 | from win32gui import * 25 | from win32gui_struct import PackMENUITEMINFO 26 | 27 | 28 | class TaskBarIcon: 29 | """ 30 | 创建任务栏图标 31 | 实现:右键菜单,通知提示 32 | """ 33 | 34 | def __init__(self, icon: str = None, hover_text: str = "TaskBarIcon", menu_style: str = "normal", 35 | menu_options: list = None, left_click=None, double_click=None, icon_x_pad: int = 7): 36 | """ 37 | Args: 38 | icon: 图标文件路径 39 | hover_text: 鼠标停留在图标上显示的文字 40 | menu_style: 菜单风格是否显示图标(normal,or iconic) 41 | menu_options: 右键菜单.菜单项格式:["菜单项名称","菜单项图标路径或None",回调函数或者子菜单列表,id数字(随便写不要重复即可)] 42 | left_click: 左键单击回调函数 43 | double_click: 左键双击回调函数 44 | icon_x_pad: 图标左边距 45 | """ 46 | # 传参 47 | self.icon = icon 48 | self.hover_text = hover_text 49 | self.menu_options = menu_options 50 | self.menu_style = menu_style 51 | self.left_click = left_click 52 | self.double_click = double_click 53 | 54 | # 设置窗体的类名,标题,消息名称。默认设置就好,一般不需要管。 55 | self.window_title = self.window_class = self.window_message = "TaskBarIconDemo" 56 | 57 | # 定义新的窗口消息,该消息保证在整个系统中是唯一的。消息值可以是在发送或发布消息时使用。 58 | msg_TaskbarRestart = RegisterWindowMessage(self.window_message) 59 | 60 | # 定义消息回调 61 | message_map = { 62 | msg_TaskbarRestart: self.OnRestart, 63 | win32con.WM_DESTROY: self.OnDestroy, 64 | win32con.WM_COMMAND: self.OnCommand, 65 | win32con.WM_USER + 20: self.OnTaskbarNotify, 66 | # owner-draw related handlers. 67 | win32con.WM_MEASUREITEM: self.OnMeasureItem, 68 | win32con.WM_DRAWITEM: self.OnDrawItem, 69 | } 70 | 71 | # Register the Window class. 72 | wc = WNDCLASS() 73 | # Returns the handle of an already loaded DLL. 74 | self.hinst = wc.hInstance = win32api.GetModuleHandle(None) 75 | wc.lpszClassName = self.window_class 76 | wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW 77 | wc.hCursor = win32api.LoadCursor(0, win32con.IDC_ARROW) 78 | wc.hbrBackground = win32con.COLOR_WINDOW 79 | wc.lpfnWndProc = message_map # could also specify a wndproc. 80 | # Don't blow up if class already registered to make testing easier 81 | try: 82 | classAtom = RegisterClass(wc) 83 | except: 84 | print("WinError.ERROR_CLASS_ALREADY_EXISTS") 85 | 86 | # Creates a new window. 87 | self.hwnd = CreateWindow( 88 | self.window_class, 89 | self.window_title, 90 | win32con.WS_OVERLAPPED | win32con.WS_SYSMENU, 91 | 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 92 | 0, 0, self.hinst, None) 93 | UpdateWindow(self.hwnd) 94 | 95 | # iconic模式下的菜单设置 96 | # Load up some information about menus needed by our owner-draw code.The font to use on the menu. 97 | ncm = SystemParametersInfo(win32con.SPI_GETNONCLIENTMETRICS) 98 | self.font_menu = CreateFontIndirect(ncm['lfMenuFont']) 99 | # spacing for our ownerdraw menus - not sure exactly what constants should be used (and if you owner-draw all 100 | # items on the menu, it doesn't matter!) 101 | self.menu_icon_height = win32api.GetSystemMetrics(win32con.SM_CYMENU) - 4 102 | self.menu_icon_width = self.menu_icon_height 103 | # space from end of icon to start of text. 104 | self.icon_x_pad = icon_x_pad 105 | 106 | self.menu_item_map = {} 107 | self.hmenu = CreatePopupMenu() 108 | 109 | # Finally, create the menu 110 | self.CreateMenu(menu=self.hmenu, menu_options=self.menu_options) 111 | 112 | # notify 通知线程 113 | self._thread = None 114 | 115 | # 设置菜单图标 116 | if icon is not None and os.path.isfile(icon): 117 | icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE 118 | self.hicon = LoadImage(self.hinst, icon, win32con.IMAGE_ICON, 0, 0, icon_flags) 119 | else: 120 | print("Icon is None") 121 | self.hicon = LoadIcon(0, win32con.IDI_APPLICATION) 122 | self._DoCreateIcons() 123 | 124 | # Runs a message loop until a WM_QUIT message is received. 125 | # 注意这是死循环,类似与tkinter中的mainloop,放到线程里面执行. 126 | # PumpMessages() 127 | 128 | def _DoCreateIcons(self): 129 | """设置后台图标""" 130 | nid = (self.hwnd, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP, win32con.WM_USER + 20, 131 | self.hicon, self.hover_text) 132 | try: 133 | # Adds, removes or modifies a taskbar icon. 134 | Shell_NotifyIcon(NIM_ADD, nid) 135 | except: 136 | # This is common when windows is starting, and this code is hit before the taskbar has been created. 137 | print("error:Failed to add the taskbar icon - is explorer running?") 138 | # but keep running anyway - when explorer starts, we get the TaskbarCreated message. 139 | 140 | def OnRestart(self, hwnd, msg, wparam, lparam): 141 | self._DoCreateIcons() 142 | 143 | def OnDestroy(self, hwnd, msg, wparam, lparam): 144 | """ 145 | 退出后台程序: 外部通过调用 win32gui.DestroyWindow(hwnd) 来退出程序 146 | """ 147 | # We need to arrange to a WM_QUIT message to be sent to our PumpMessages() loop. 148 | nid = (self.hwnd, 0) 149 | Shell_NotifyIcon(NIM_DELETE, nid) 150 | PostQuitMessage(0) # Terminate the app. 151 | 152 | def OnTaskbarNotify(self, hwnd, msg, wparam, lparam): 153 | """ 154 | 实际上最主要的就三种:左键,右键,双击左键 155 | """ 156 | if lparam == win32con.WM_LBUTTONUP: 157 | # print("You clicked me.") 158 | if self.left_click is not None: 159 | if type(self.left_click) == tuple: 160 | if len(self.left_click) == 2: 161 | self.left_click[0](*self.left_click[1]) 162 | else: 163 | self.left_click[0]() 164 | else: 165 | self.left_click() 166 | else: 167 | pass 168 | elif lparam == win32con.WM_LBUTTONDBLCLK: 169 | # print("You double-clicked me.") 170 | if self.double_click is not None: 171 | if type(self.double_click) == tuple: 172 | if len(self.double_click) == 2: 173 | self.double_click[0](*self.double_click[1]) 174 | else: 175 | self.double_click[0]() 176 | else: 177 | self.double_click() 178 | else: 179 | pass 180 | elif lparam == win32con.WM_RBUTTONUP: 181 | # print("You right clicked me.") 182 | # display the menu at the cursor pos. 183 | pos = GetCursorPos() 184 | # See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp 185 | SetForegroundWindow(self.hwnd) 186 | TrackPopupMenu(self.hmenu, win32con.TPM_LEFTALIGN, pos[0], pos[1], 0, self.hwnd, None) 187 | PostMessage(self.hwnd, win32con.WM_NULL, 0, 0) 188 | 189 | return 1 190 | 191 | def OnCommand(self, hwnd, msg, wparam, lparam): 192 | """ 193 | 回调函数:根据id来判断 194 | """ 195 | # An interface to the win32api LOWORD macro. 196 | id = win32api.LOWORD(wparam) 197 | if self.menu_item_map[id][2] is not None: 198 | if type(self.menu_item_map[id][2]) == tuple: 199 | if len(self.menu_item_map[id][2]) == 2: 200 | self.menu_item_map[id][2][0](*self.menu_item_map[id][2][1]) 201 | else: 202 | self.menu_item_map[id][2][0]() 203 | else: 204 | self.menu_item_map[id][2]() 205 | else: 206 | pass 207 | # print(self.menu_item_map[id][0], ":没有设置回调函数") 208 | 209 | def OnMeasureItem(self, hwnd, msg, wparam, lparam): 210 | """ 211 | Owner-draw related functions. We only have 1 owner-draw item, but we pretend we have more than that :) 212 | """ 213 | # Last item of MEASUREITEMSTRUCT is a ULONG_PTR 214 | fmt = "5iP" 215 | buf = PyMakeBuffer(struct.calcsize(fmt), lparam) 216 | data = struct.unpack(fmt, buf) 217 | ctlType, ctlID, itemID, itemWidth, itemHeight, itemData = data 218 | 219 | text, hicon, func, index = self.menu_item_map[itemData] 220 | if text is None: 221 | # Only drawing icon due to HBMMENU_CALLBACK 222 | cx = self.menu_icon_width 223 | cy = self.menu_icon_height 224 | else: 225 | # drawing the lot! 226 | dc = GetDC(hwnd) 227 | oldFont = SelectObject(dc, self.font_menu) 228 | cx, cy = GetTextExtentPoint32(dc, text) 229 | SelectObject(dc, oldFont) 230 | ReleaseDC(hwnd, dc) 231 | 232 | cx += win32api.GetSystemMetrics(win32con.SM_CXMENUCHECK) 233 | cx += self.menu_icon_width + self.icon_x_pad 234 | 235 | cy = win32api.GetSystemMetrics(win32con.SM_CYMENU) 236 | 237 | new_data = struct.pack(fmt, ctlType, ctlID, itemID, cx, cy, itemData) 238 | PySetMemory(lparam, new_data) 239 | return True 240 | 241 | def OnDrawItem(self, hwnd, msg, wparam, lparam): 242 | # lparam is a DRAWITEMSTRUCT 243 | fmt = "5i2P4iP" 244 | data = struct.unpack(fmt, PyGetMemory(lparam, struct.calcsize(fmt))) 245 | ctlType, ctlID, itemID, itemAction, itemState, hwndItem, \ 246 | hDC, left, top, right, bot, itemData = data 247 | 248 | rect = left, top, right, bot 249 | 250 | text, hicon, _, _ = self.menu_item_map[itemData] 251 | 252 | if text is None: 253 | # This means the menu-item had HBMMENU_CALLBACK - so all we 254 | # draw is the icon. rect is the entire area we should use. 255 | DrawIconEx(hDC, left, top, hicon, right - left, bot - top, 256 | 0, 0, win32con.DI_NORMAL) 257 | else: 258 | # If the user has selected the item, use the selected 259 | # text and background colors to display the item. 260 | selected = itemState & win32con.ODS_SELECTED 261 | if selected: 262 | crText = SetTextColor(hDC, GetSysColor(win32con.COLOR_HIGHLIGHTTEXT)) 263 | crBkgnd = SetBkColor(hDC, GetSysColor(win32con.COLOR_HIGHLIGHT)) 264 | 265 | each_pad = self.icon_x_pad // 2 266 | x_icon = left + win32api.GetSystemMetrics(win32con.SM_CXMENUCHECK) + each_pad 267 | x_text = x_icon + self.menu_icon_width + each_pad 268 | 269 | # Draw text first, specifying a complete rect to fill - this sets 270 | # up the background (but overwrites anything else already there!) 271 | # Select the font, draw it, and restore the previous font. 272 | hfontOld = SelectObject(hDC, self.font_menu) 273 | ExtTextOut(hDC, x_text, top + 2, win32con.ETO_OPAQUE, rect, text) 274 | SelectObject(hDC, hfontOld) 275 | 276 | # Icon image next. Icons are transparent - no need to handle 277 | # selection specially 278 | if hicon is not None and type(hicon) == int: 279 | DrawIconEx(hDC, x_icon, top + 2, hicon, 280 | self.menu_icon_width, self.menu_icon_height, 281 | 0, 0, win32con.DI_NORMAL) 282 | else: 283 | # print(text,":当前选项设置icon失败") 284 | pass 285 | 286 | # Return the text and background colors to their 287 | # normal state (not selected). 288 | if selected: 289 | SetTextColor(hDC, crText) 290 | SetBkColor(hDC, crBkgnd) 291 | 292 | def CreateMenu(self, menu=None, menu_options=None): 293 | """设置菜单""" 294 | if self.menu_style == "normal" and menu_options is not None: 295 | for item in menu_options: 296 | self.menu_item_map[item[3]] = item 297 | 298 | if item[0] == "分隔符": 299 | InsertMenu(menu, 0, win32con.MF_BYPOSITION, win32con.MF_SEPARATOR, None) 300 | continue 301 | 302 | if type(item[2]) == list: 303 | # print("当前是submenu") 304 | submenu = CreatePopupMenu() 305 | self.CreateMenu(submenu, item[2]) 306 | item, extras = PackMENUITEMINFO(text=item[0], hSubMenu=submenu, wID=item[3]) 307 | InsertMenuItem(menu, 0, 1, item) 308 | continue 309 | 310 | item, extras = PackMENUITEMINFO(text=item[0], wID=item[3]) 311 | 312 | InsertMenuItem(menu, 0, 1, item) 313 | 314 | elif self.menu_style == "iconic" and menu_options is not None: 315 | for item in menu_options: 316 | self.menu_item_map[item[3]] = item 317 | # Owner-draw menus mainly from: 318 | # http://windowssdk.msdn.microsoft.com/en-us/library/ms647558.aspx 319 | # and: 320 | # http://www.codeguru.com/cpp/controls/menu/bitmappedmenus/article.php/c165 321 | 322 | # Create one with an icon - this is *lots* more work - we do it 323 | # owner-draw! The primary reason is to handle transparency better - 324 | # converting to a bitmap causes the background to be incorrect when 325 | # the menu item is selected. I can't see a simpler way. 326 | # First, load the icon we want to use. 327 | ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON) 328 | ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON) 329 | 330 | if self.menu_item_map[item[3]][1]: 331 | try: 332 | hicon = LoadImage(0, self.menu_item_map[item[3]][1], win32con.IMAGE_ICON, ico_x, ico_y, 333 | win32con.LR_LOADFROMFILE) 334 | self.menu_item_map[item[3]][1] = hicon 335 | except: 336 | print(self.menu_item_map[item[3]][1], ":没找到文件,文件名称是否正确?") 337 | 338 | if item[0] == "分隔符": 339 | InsertMenu(menu, 0, win32con.MF_BYPOSITION, win32con.MF_SEPARATOR, None) 340 | continue 341 | 342 | if type(item[2]) == list: 343 | # print("当前是submenu") 344 | sub_menu = CreatePopupMenu() 345 | self.CreateMenu(sub_menu, item[2]) 346 | 347 | item, extras = PackMENUITEMINFO( 348 | fType=win32con.MFT_OWNERDRAW, 349 | dwItemData=item[3], 350 | wID=item[3], 351 | hSubMenu=sub_menu) 352 | InsertMenuItem(menu, 0, 1, item) 353 | continue 354 | 355 | item, extras = PackMENUITEMINFO( 356 | fType=win32con.MFT_OWNERDRAW, 357 | dwItemData=item[3], 358 | wID=item[3]) 359 | InsertMenuItem(menu, 0, 1, item) 360 | 361 | else: 362 | pass 363 | 364 | def notification_active(self): 365 | """是否正在有通知显示""" 366 | if self._thread is not None and self._thread.is_alive(): 367 | # We have an active notification, let is finish we don't spam them 368 | return True 369 | return False 370 | 371 | def ShowToast(self, title: str = "通知", msg: str = "请写下你的信息内容", duration: int = 0): 372 | """ 373 | 通知栏弹窗Toast 374 | 375 | Args: 376 | title: 标题 377 | msg: 内容 378 | duration: 通知提醒的最短时间(在这段时间内不会有新的通知替换掉它) 379 | """ 380 | if self.notification_active(): 381 | # We have an active notification, let is finish so we don't spam them 382 | print("当前正在展示通知中...") 383 | return False 384 | 385 | # self._thread = threading.Thread( 386 | # target=Shell_NotifyIcon, 387 | # args=(NIM_MODIFY, ( 388 | # self.hwnd, 389 | # 0, 390 | # NIF_INFO, 391 | # WM_USER + 20, 392 | # self.hicon, 393 | # "Balloon Tooltip", 394 | # msg, 395 | # duration, 396 | # title))) 397 | 398 | self._thread = threading.Thread( 399 | target=self._show_toast, 400 | args=(title, msg, duration)) 401 | self._thread.start() 402 | 403 | def _show_toast(self, title, msg, duration): 404 | Shell_NotifyIcon(NIM_MODIFY, ( 405 | self.hwnd, 406 | 0, 407 | NIF_INFO, 408 | WM_USER + 20, 409 | self.hicon, 410 | "Balloon Tooltip", 411 | msg, 412 | duration, 413 | title)) 414 | time.sleep(duration) 415 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @Project :pywin10 4 | @File :setup.py.py 5 | @Author :Gao yongxian 6 | @Date :2021/11/30 13:24 7 | @contact: g1695698547@163.com 8 | """ 9 | import setuptools 10 | 11 | with open("README.md", "r", encoding="utf-8") as fh: 12 | long_description = fh.read() 13 | 14 | setuptools.setup( 15 | name="pywin10", 16 | version="0.0.3", 17 | author="Gaoyongxian666", 18 | author_email="g1695698547@163.com", 19 | description="基于Pywin32,封装了系统托盘,右键菜单,win10通知栏等功能", 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | url="https://github.com/Gaoyongxian666/pywin10", 23 | packages=setuptools.find_packages(), 24 | classifiers=[ 25 | "Programming Language :: Python :: 3", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | ], 29 | install_requires=["pywin32"], 30 | include_package_data=True, 31 | project_urls={ 32 | 'Bug Reports': 'https://github.com/Gaoyongxian666/pywin10', 33 | 'Source': 'https://github.com/Gaoyongxian666/pywin10', 34 | }, 35 | ) 36 | 37 | -------------------------------------------------------------------------------- /主页.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/主页.ico -------------------------------------------------------------------------------- /日历.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/日历.ico -------------------------------------------------------------------------------- /等待文件.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/等待文件.ico -------------------------------------------------------------------------------- /编辑.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/编辑.ico -------------------------------------------------------------------------------- /退出.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gaoyongxian666/pywin10/812910e08be05d5a6b9ac20d286832b1210ace14/退出.ico --------------------------------------------------------------------------------