Python添加windows资源管理器上下文菜单 无条目限制
目前开发一个项目x1ntt/pychee6需要在资源管理器的上下文菜单中插入命令,于是调查了一下python能用的库。
目前来说,最好用的库是Context_ment
,接口设计简单,使用方便,同时还兼容Linux系统;再者就是通过使用COM
的方式让资源管理器主动调用Python
脚本。这种方案条目限制比较宽松,能有几百个条目,使用上也会更自由一点(繁琐)。
Context_menu
其基本原理是通过修改注册表来实现在资源管理器中添加条目,但是当条目超过16个的时候,多出来的部分不会显示。我猜这个是因为早期电脑性能不好,如果条目过多,上下文菜单需要很久才能弹出来,所以有这个限制(xp时代)。
具体使用例子可以看项目Readme,如果需要中文例子,可以看:Python跨平台上下文菜单库 —— context_menu
使用指南-CSDN博客
如果只是为了实现在上下文菜单中添加一两个命令,那么这个库就是最好的选择,忽略下面吧。
COM方案的实现
但是我的需求需要实现将lychee
中的所有相册结构映射到上下文菜单中,数量会非常多,所以只能使用这个方案。具体可以看我的x1ntt/pychee6_cm项目。
找了很久,没有一个现成的库实现COM
方案来做这件事,但是我找到了一个例子代码:py-tools/ShellExtension at master · winterTTr/py-tools (具体见shell_extension.py
文件)。
这个例子是基于pywin32
库实现的,它是python2
编写的,可以交给AI
改写为Python3
。
简述COM
COM
技术是微软公司制定的一种Windows平台下的软件模块复用技术。借助于COM技术,用户可以编写一些具有特定接口的软件模块(称为COM组件 ),它们可以以dll或exe的形式注册到Windows系统中来对外公开自己,其它的应用程序则可以借助于Windows提供的API来调用这些组件,从而在整个系统范围内实现了软件模块的复用 COM开发指南(1)—COM技术概述-CSDN博客
如果不打算深究这个技术,可以简单理解成server
和client
的关系。通过windows api
实现一个server
,同时实现一些接口,然后给server
分配一个GUID
,client
就能根据GUID
连接到server
。至此,双方通过约定好的接口通信即可。
可以在com_objects找到pywin32
实现的一些com
接口。
要实现修改资源管理器上下文的功能,我们需要关注PyIShellExtInit
和PyIContextMenu
这两个接口
一个添加上下文菜单的例子
这里以我的x1ntt/pychee6_cm: 用于注册pychee.cli到windows资源管理的上下文菜单 项目作为例子。
src
目录中有如下文件
代码文件 | 作用 |
---|---|
reg.py | 代码逻辑,实现com组件 |
register.bat | 将reg.py中实现的组件注册到注册表 |
unregister.bat | 取消注册 |
debug_register.bat | 启用调试模式的注册,输出调试信息到调试窗口 |
open_trace_window.bat | 打开调试窗口,本质是运行了site-packages\win32\lib\win32traceutil.py 需要调整为正确路径 |
restart_explorer.bat | 重启资源管理器 |
修改过reg.py
文件后,必须通过restart_explorer.bat
重启资源管理器代码才会生效,重新注册不会生效。
例子解析
见pychee6_cm
项目的reg.py
文件
将类注册到注册表
from win32com.server import register
register.UseCommandLine(ShellExtension,
finalize_register=DllRegisterServer,
finalize_unregister=DllUnregisterServer)
导入win32com.server
,将注册ShellExtension
类,同时指定注册和取消注册时的回调函数。
DllRegisterServer
函数将clsid
(其实就是上面说的GUID),写进注册表,告诉资源管理器出现如下情况时调用clsid
对应的com
组件
注册表路径 | 含义 |
---|---|
*\\shellex\\ContextMenuHandlers\\ |
任意文件右键 |
directory\\shellex\\ContextMenuHandlers\\ |
文件夹右键 |
directory\\background\\shellex\\ContextMenuHandlers\\ |
进入文件夹后右键空白处 |
Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved |
告知资源管理器使用这个组件 |
ShellExtension
这个就是实现的com
组件,需要关注的重点是:
-
_reg_clsid_
就是组件的唯一id
也就是写进注册表的值,资源管理器通过这个值找到组件,可以通过任意方式得到诸如{7C15377B-26A0-4582-A594-12F95CABD4A2}
的值(GUID)。 -
_com_interfaces_
表明该组件实现了什么接口,提供了什么功能,例如例子中提供了IID_IShellExtInit
和IID_IContextMenu
(详细文档PyIShellExtInit Object、PyIContextMenu Object)
接下来实现两个接口对应的几个函数即可
函数名 | 归属接口 | 调用时机及含义 |
---|---|---|
Initialize | IID_IShellExtInit | 点击右键时,用于获取待操作的目录或选中的文件 |
QueryContextMenu | IID_IContextMenu | 点击右键时,用于提供菜单条目,在这里编辑菜单 |
InvokeCommand | IID_IContextMenu | 点击具体条目时,调用对应的命令 |
GetCommandString | IID_IContextMenu | 检索上下文菜单选项的动词或帮助文本 |
通过debug_register.bat
注册之后,右键打开菜单,应该能在调试窗口中看到上面函数被调用。
Initialize
获取上下文菜单需要操作的目标(菜单上下文)
QueryContextMenu
方法中使用win32gui.InsertMenu
函数构建菜单,这里需要注意的一点是QueryContextMenu
参数中提供的idCmdFirst, idCmdLast
两个参数,指定了InsertMenu
的idCmd
取值范围,如果超过这个范围,可能会导致混乱(具体我也不知道会发生什么),所以这个方案其实还是有条目数量限制的。
至于InsertMenu
函数的参数含义,可以看这里:InsertMenuW 函数 (winuser.h) - Win32 apps | Microsoft Learn。因为pywin32
本身是对windows api
的封装,所以直接参考其开发手册应该没有问题。
InvokeCommand
参数传递的verb
参数用来标记用户执行了那个条目,我本来以为这是idCmd
的作用,但实际上verb
只是个索引,它表示执行你插入的第几个菜单条目。
GetCommandString
我不知道它的作用是什么,目前没看出来实际作用。
最终实现的效果