news 2026/8/8 10:07:31

旧版快捷菜单(IContextMenu)实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
旧版快捷菜单(IContextMenu)实现

图例

文件资源管理器右键菜单 | | V 初始化 IShellExtInit | | 调用 Initialize V 启用 IContextMenu (快捷菜单拓展接口) | | 调用 QueryContextMenu V InsertMenuItem 对象1 -> 显示为菜单项1 InsertMenu 对象2 -> 显示为菜单项2 (可能是带子菜单的项) | | 调用 InvokeCommand V 执行命令

IShellExtInit的实现

/** [in] pidlFolder 类型: PCIDLIST_ABSOLUTE 指向唯一标识文件夹的 ITEMIDLIST 结构的指针。 对于属性表扩展,此参数为 NULL。 对于快捷菜单扩展,它是包含要显示其快捷菜单的项的文件夹的项目标识符列表。 对于非默认拖放菜单扩展,此参数指定目标文件夹。 [in] pdtobj 类型: IDataObject* 指向 IDataObject 接口对象的指针,该对象可用于检索正在执行操作的对象。 [in] hkeyProgID 类型: HKEY 文件对象或文件夹类型的注册表项。 */ HRESULT Initialize( _In_ PCIDLIST_ABSOLUTE pidlFolder, _In_ IDataObject* pdtobj, _In_ HKEY hkeyProgID );

案例

private: std::vector<std::wstring> m_selectedFiles; std::wstring m_folderPath; public: STDMETHOD(Initialize)( _In_ PCIDLIST_ABSOLUTE pidlFolder, _In_ IDataObject* pdtobj, _In_ HKEY hkeyProgID )override{ // 获取文件夹folderPath TCHAR szFolderPath[MAX_PATH] = { 0 }; if (pidlFolder != NULL) { if (SHGetPathFromIDListW(pidlFolder, szFolderPath)) { // szFolderPath 现在包含文件夹的完整路径 // 例如:C:\Users\Username\Documents m_folderPath = szFolderPath; } } // 获取选中的文件 if (pdtobj) { FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg = { 0 }; if (SUCCEEDED(pdtobj->GetData(&fmt, &stg))) { HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal); if (hDrop) { UINT nFiles = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0); for (UINT i = 0; i < nFiles; i++) { wchar_t szFile[MAX_PATH]; if (DragQueryFileW(hDrop, i, szFile, MAX_PATH)) { m_selectedFiles.push_back(szFile); } } GlobalUnlock(stg.hGlobal); } ReleaseStgMedium(&stg); } } return S_OK; }

IContextMenu的实现

方法声明
STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override; STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO pici) override; STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) override;
/// <summary> /// 获取有关快捷菜单命令的信息,包括命令的帮助字符串和与语言无关或 规范的名称。 /// </summary> /// <param name="idCmd">菜单命令标识符偏移量</param> /// <param name="uType">指定要返回的信息的标志。 </param> /// <param name="pReserved">保留。 应用程序在调用此方法时必须指定 NULL ,处理程序在调用时必须忽略此参数。</param> /// <param name="pszName">要接收正在检索的以 null 结尾的字符串的缓冲区的地址。</param> /// <param name="cchMax">要接收以 null 结尾的字符串的缓冲区的大小(以字符为单位)。</param> /// <returns>S_OK</returns> HRESULT GetCommandString( UINT_PTR idCmd, UINT uType, UINT* pReserved, CHAR* pszName, UINT cchMax ); /// <summary> /// 执行与快捷菜单项关联的命令。 /// </summary> /// <param name="pici">指向 CMINVOKECOMMANDINFO 或 CMINVOKECOMMANDINFOEX 结构的指针,该结构包含有关命令的详细信息。</param> /// <returns>S_OK</returns> HRESULT InvokeCommand( CMINVOKECOMMANDINFO* pici ); /// <summary> /// 将命令添加到快捷菜单。 /// </summary> /// <param name="hmenu">快捷菜单的句柄。 添加菜单项时,处理程序应指定此句柄。</param> /// <param name="indexMenu">要插入第一个新菜单项的从零开始的位置。</param> /// <param name="idCmdFirst">处理程序可以为菜单项标识符指定的最小值。</param> /// <param name="idCmdLast">处理程序可以为菜单项标识符指定的最大值。</param> /// <param name="uFlags">指定如何更改快捷菜单的可选标志。 可多选</param> /// <returns>S_OK</returns> HRESULT QueryContextMenu( HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags );
QueryContextMenu
/// <summary> /// 将程序菜单添加到快捷菜单。 /// </summary> /// <param name="hmenu">快捷菜单的句柄。 添加菜单项时,处理程序应指定此句柄。</param> /// <param name="indexMenu"> 定位参数,要插入第一个新菜单项的从零开始的位置。 /// 定位菜单程序总快捷菜单中的位置,0为首,-1为末 /// </param> /// <param name="idCmdFirst">第一个命令ID,处理程序可以为菜单项标识符指定的最小值。</param> /// <param name="idCmdLast">最后一个命令ID,处理程序可以为菜单项标识符指定的最大值。</param> /// <param name="uFlags">指定如何更改快捷菜单的可选标志。 可多选 // uFlags 简要说明: // CMF_DEFAULTONLY : (最重要!) 用户意图为双击/回车执行默认操作,而非打开菜单。应立即返回,不添加任何项。 // CMF_EXTENDEDVERBS : 通常由 Shift+右键 触发,应显示高级/扩展菜单项。 // CMF_VERBSONLY : 通常来自“发送到”菜单,应仅添加核心操作命令,避免分隔符等。 // CMF_EXPLORE : 菜单在资源管理器窗口内弹出,而非桌面或对话框。 // CMF_CANRENAME : 当前选择项支持重命名操作(通常为单个对象)。 // CMF_ASYNCVERBSTATE : 允许异步计算菜单项状态(启用/禁用),用于避免耗时操作阻塞菜单弹出。 // CMF_DISABLEDVERBS : 应显示即使为灰色不可用的菜单项。 // CMF_NORMAL : 无特殊标志,常规菜单模式。 // CMF_NODEFAULT : 菜单不应包含默认命令(无粗体项)。 // CMF_INCLUDESTATIC : (主要用于旧式静态扩展) 应包含静态菜单项。 // CMF_RESERVED : 保留标志,应忽略。 /// CMF_NOVERBS × /// CMF_ITEMMENU × /// CMF_OPTIMIZEFORINVOKE × /// CMF_SYNCCASCADEMENU × /// CMF_DONOTPICKDEFAULT × /// /// </param> /// <returns>S_OK</returns> HRESULT QueryContextMenu( HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags );
InsertMenu
/// <summary> /// 创建普通菜单并绑定到程序菜单上。 /// </summary> /// <param name="hmenu">快捷菜单的句柄。</param> /// <param name="uPosition">由uFlags决定</param> /// <param name="uFlags"> /// MF_BYCOMMAND : uPosition为标识符 /// MF_BYPOSITION : uPosition为菜单相对位置 /// MF_BITMAP : lpNewItem包含位图 /// MF_CHECKED : 复选标记 /// MF_DISABLED : 禁用模式(不灰色) /// MF_ENABLED : 启用模式 /// MF_GRAYED : 禁用模式(灰色) /// MF_MENUBARBREAK : 新列通过垂直线与旧列分割 /// MF_MENUBREAK : 新列不分割 /// MF_OWNERDRAW : 自定义菜单lpNewItem /// MF_POPUP : uIDNewItem包含子菜单 /// MF_SEPARATOR : 水平分隔线 /// MF_STRING : lpNewItem是文本字符串 /// MF_UNCHECKED : 取消复选标记 /// </param> /// <param name="uIDNewItem">新菜单项的标识符或子菜单的句柄</param> /// <param name="lpNewItem"> /// 由uFlags决定 /// 如果uFlags = MF_BITMAP : 位图句柄 /// 如果uFlags = MF_OWNERDRAW : 自定义菜单参数 /// 如果uFlags = MF_STRING : 字符串指针 /// </param> BOOL InsertMenuA( HMENU hMenu, UINT uPosition, UINT uFlags, UINT_PTR uIDNewItem, LPCSTR lpNewItem );
InsertMenuItem
/// <summary> /// 创建普通菜单并绑定到程序菜单上。 /// </summary> /// <param name="hmenu">快捷菜单的句柄</param> /// <param name="item">由fByPosition决定</param> /// <param name="fByPosition"> /// FALSE : 菜单项标识符 /// TRUE : 菜单项位置 /// </param> /// <param name="lpmi"> /// 指向 MENUITEMINFOA 结构体 /// </param> BOOL InsertMenuItemA( HMENU hmenu, UINT item, BOOL fByPosition, LPCMENUITEMINFOA lpmi );
MENUITEMINFO
/* UINT cbSize; 结构的大小,直接设置为sizeof(MENUITEMINFO) UINT fMask; 配置项 MIIM_BITMAP 开启配置hbmpItem参数 MIIM_CHECKMARKS 开启配置hbmpChecked 和 hbmpUnchecked 参数 MIIM_DATA 开启配置dwItemData 参数 MIIM_FTYPE 开启配置fType 参数 MIIM_ID 开启配置wID 参数 MIIM_STATE 开启配置fState 参数 MIIM_STRING 开启配置dwTypeData 参数 MIIM_SUBMENU 开启配置hSubMenu 参数 MIIM_TYPE 开启配置fType 和 dwTypeData 参数 UINT fType; MFT_BITMAP 启用位图,dwTypeData低位为位图句柄 MFT_MENUBARBREAK 垂直线项新列与旧列分开 MFT_MENUBREAK 垂直线项新列不会与旧列分开 MFT_OWNERDRAW 自定义菜单,配置dwTypeData值 MFT_RADIOCHECK 由hbmpChecked决定,单选按钮 MFT_RIGHTJUSTIFY 右对齐菜单项和任何后续项 MFT_RIGHTORDER 指定菜单从右向左级联 MFT_SEPARATOR 水平分隔线 MFT_STRING 启用文本字符串,dwTypeData参数为字符串句柄 UINT fState; MFS_CHECKED 启用hbmpChecked 项 MFS_DEFAULT 指定菜单项为默认值,菜单只能包含一个默认菜单项,以粗体显示。 MFS_DISABLED 禁用菜单项并将其灰显&& MFS_ENABLED 启用菜单项 MFS_GRAYED 禁用菜单项并将其灰显&& MFS_HILITE 突出显示菜单项 MFS_UNCHECKED 取消选中菜单项 MFS_UNHILITE 从菜单项中删除突出显示 UINT wID; 标识菜单项的应用程序定义值 HMENU hSubMenu; 下拉菜单或子菜单的句柄;可为NULL HBITMAP hbmpChecked; 可选框 HBITMAP hbmpUnchecked; 可选框 ULONG_PTR dwItemData; LPSTR dwTypeData; UINT cch; HBITMAP hbmpItem; HBMMENU_CALLBACK 自定义 HBMMENU_MBAR_CLOSE 菜单栏的“关闭”按钮。 HBMMENU_MBAR_CLOSE_D 菜单栏的“已禁用关闭”按钮。 HBMMENU_MBAR_MINIMIZE 菜单栏的最小化按钮 HBMMENU_MBAR_MINIMIZE_D 已禁用菜单栏的最小化按钮。 HBMMENU_MBAR_RESTORE 菜单栏的“还原”按钮。 HBMMENU_POPUP_CLOSE 子菜单的“关闭”按钮。 HBMMENU_POPUP_MAXIMIZE 子菜单的最大化按钮 HBMMENU_POPUP_MINIMIZE 子菜单的最小化按钮。 HBMMENU_POPUP_RESTORE 子菜单的“还原”按钮。 HBMMENU_SYSTEM dwItemData中指定的窗口的 Windows 图标或图标。 */ typedef struct tagMENUITEMINFOA { UINT cbSize; UINT fMask; UINT fType; UINT fState; UINT wID; HMENU hSubMenu; HBITMAP hbmpChecked; HBITMAP hbmpUnchecked; ULONG_PTR dwItemData; LPSTR dwTypeData; UINT cch; HBITMAP hbmpItem; } MENUITEMINFOA, *LPMENUITEMINFOA;
InvokeCommand
/// <summary> /// 执行与快捷菜单项关联的命令。 /// </summary> /// <param name="pici">指向 CMINVOKECOMMANDINFO 或 CMINVOKECOMMANDINFOEX 结构的指针,该结构包含有关命令的详细信息。</param> /// <returns>S_OK</returns> HRESULT InvokeCommand( CMINVOKECOMMANDINFO* pici );
CMINVOKECOMMANDINFO
/* DWORD cbSize; 此结构的大小 DWORD fMask; 标志位 CMIC_MASK_HOTKEY 激活dwHotKey参数键盘快捷方式 CMIC_MASK_ICON 激活hIcon参数图标 CMIC_MASK_FLAG_NO_UI 系统无法显示用户界面元素 CMIC_MASK_NO_CONSOLE 禁止创建新控制台 CMIC_MASK_FLAG_SEP_VDM 16 位应用程序时才有效,在专用虚拟 DOS 中运行 CMIC_MASK_ASYNCOK 等待 DDE 会话终止,然后再返回 CMIC_MASK_NOASYNC CMIC_MASK_SHIFT_DOWN 已按下 SHIFT 键 CMIC_MASK_CONTROL_DOWN 已按下 CTRL 键 CMIC_MASK_FLAG_LOG_USAGE 指示InvokeCommand的实现可能需要跟踪为“最近使用的文档”菜单等功能调用的项。 CMIC_MASK_NOZONECHECKS 不要执行区域检查 HWND hwnd; 父窗口句柄 LPCSTR lpVerb; 命令行为 CMDSTR_RUNAS Runas CMDSTR_PRINT 打印 CMDSTR_PREVIEW 预览 CMDSTR_OPEN 打开 LPCSTR lpParameters; 命令参数 LPCSTR lpDirectory; 工作目录 int nShow; 窗口显示参数 DWORD dwHotKey; 指定键盘快捷方式,由fMask的 CMIC_MASK_HOTKEY 控制 HANDLE hIcon; 指定图标,由fMask的 CMIC_MASK_ICON 控制 */ typedef struct _CMINVOKECOMMANDINFO { DWORD cbSize; DWORD fMask; HWND hwnd; LPCSTR lpVerb; LPCSTR lpParameters; LPCSTR lpDirectory; int nShow; DWORD dwHotKey; HANDLE hIcon; } CMINVOKECOMMANDINFO;
CMINVOKECOMMANDINFOEX
/* DWORD cbSize; 此结构的大小 DWORD fMask; 标志位 CMIC_MASK_HOTKEY 激活dwHotKey参数键盘快捷方式 CMIC_MASK_ICON 激活hIcon参数图标 CMIC_MASK_FLAG_NO_UI 系统无法显示用户界面元素 CMIC_MASK_UNICODE 激活Unicode字符串数据,包括 lpVerbW、 lpParametersW、 lpDirectoryW 和 lpTitleW 成员 CMIC_MASK_NO_CONSOLE 禁止创建新控制台 CMIC_MASK_HASLINKNAME lpTitle 成员包含快捷方式文件的完整路径 CMIC_MASK_HASTITLE 激活 lpTitle 参数 CMIC_MASK_FLAG_SEP_VDM 16 位应用程序时才有效,在专用虚拟 DOS 中运行 CMIC_MASK_ASYNCOK 等待 DDE 会话终止,然后再返回 CMIC_MASK_NOASYNC CMIC_MASK_SHIFT_DOWN 已按下 SHIFT 键 CMIC_MASK_CONTROL_DOWN 已按下 CTRL 键 CMIC_MASK_FLAG_LOG_USAGE 指示InvokeCommand的实现可能需要跟踪为“最近使用的文档”菜单等功能调用的项。 CMIC_MASK_NOZONECHECKS 不要执行区域检查 CMIC_MASK_PTINVOKE 激活 ptInvoke 参数 HWND hwnd; 父窗口句柄 LPCSTR lpVerb; 命令行为 CMDSTR_RUNAS Runas CMDSTR_PRINT 打印 CMDSTR_PREVIEW 预览 CMDSTR_OPEN 打开 LPCSTR lpParameters; 命令参数 LPCSTR lpDirectory; 工作目录 int nShow; 窗口显示参数 DWORD dwHotKey; 指定键盘快捷方式,由fMask的 CMIC_MASK_HOTKEY 控制 HANDLE hIcon; 指定图标,由fMask的 CMIC_MASK_ICON 控制 LPCSTR lpTitle; LPCWSTR lpVerbW; Unicode 谓词 LPCWSTR lpParametersW; Unicode 参数 LPCWSTR lpDirectoryW; Unicode 目录 LPCWSTR lpTitleW; Unicode 标题 POINT ptInvoke; 命令调用点 */ typedef struct _CMINVOKECOMMANDINFOEX { DWORD cbSize; DWORD fMask; HWND hwnd; LPCSTR lpVerb; LPCSTR lpParameters; LPCSTR lpDirectory; int nShow; DWORD dwHotKey; HANDLE hIcon; LPCSTR lpTitle; LPCWSTR lpVerbW; LPCWSTR lpParametersW; LPCWSTR lpDirectoryW; LPCWSTR lpTitleW; POINT ptInvoke; } CMINVOKECOMMANDINFOEX;
GetCommandString
/** * @brief 获取有关快捷菜单命令的信息,包括命令的帮助字符串和与语言无关或 规范的名称。 * @param idCmd [in]菜单命令标识符偏移量 * @param uType [in]指定要返回的信息的标志 * GCS_HELPTEXTA (ANSI)将 pszName 设置为包含命令帮助文本 * GCS_HELPTEXTW (Unicode)将 pszName 设置为包含命令帮助文本 * GCS_VALIDATEA 如果菜单项存在,则返回S_OK,否则返回S_FALSE。 * GCS_VALIDATEW * GCS_VERBA 将 pszName 设置为包含菜单项与语言无关的命令名称的字符串 * GCS_VERBW * @param pReserved 保留。 应用程序在调用此方法时必须指定 NULL ,处理程序在调用时必须忽略此参数。 * @param pszName [out]要接收正在检索的以 null 结尾的字符串的缓冲区的地址。 * @param cchOut [out]要接收以 null 结尾的字符串的缓冲区的大小(以字符为单位) * @return S_OK * @summary * */ HRESULT GetCommandString( UINT_PTR idCmd, UINT uType, UINT* pReserved, CHAR* pszName, UINT cchMax );

注册表结构

IContextMenu的注册表需求分为两部分,一部分是标记拓展组件唯一标识的CLSID key,另一部分是注册到右键菜单指定环境的 <file_extension> Key ,如全部文件的* 和AllFilesystemObjects 、DesktopBackground桌面背景目录等,详细参考COM注册表项架构。

HKCR { <file_extension> { shell {...} shellex {...} } CLSID { ...ATL项目自动生成 } }

参考链接

微软文档-IContextMenu (shobjidl_core.h) - Win32 apps | Microsoft Learn
微软文档-InsertMenuA 函数 (winuser.h) - Win32 apps | Microsoft Learn
微软文档-InsertMenuItemA 函数 (winuser.h) - Win32 apps | Microsoft Learn
微软文档-IContextMenu::GetCommandString (shobjidl_core.h) - Win32 apps | Microsoft Learn
微软文档-IContextMenu::InvokeCommand (shobjidl_core.h) - Win32 apps | Microsoft Learn
微软文档-CMINVOKECOMMANDINFO (shobjidl_core.h) - Win32 apps | Microsoft Learn
微软文档-CMINVOKECOMMANDINFOEX (shobjidl_core.h) - Win32 apps | Microsoft Learn

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 10:07:28

释放你的音乐自由:3分钟学会使用ncmdumpGUI解密网易云音乐NCM文件

释放你的音乐自由&#xff1a;3分钟学会使用ncmdumpGUI解密网易云音乐NCM文件 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾为网易云音乐下载的歌曲…

作者头像 李华
网站建设 2026/8/8 10:00:42

Qt QProcess执行Linux管道命令的三种解决方案与实战指南

1. 问题现象与根源剖析最近在做一个跨平台的系统监控工具&#xff0c;用Qt的QProcess组件去调用Linux系统命令获取信息&#xff0c;比如想用ps aux | grep myapp来过滤进程。代码写起来很简单&#xff0c;QProcess process; process.start("ps aux | grep myapp");&a…

作者头像 李华
网站建设 2026/8/8 9:59:01

大模型移动端部署实战:llama.cpp量化与Android手机本地运行指南

这次我们来看一个非常实际的问题&#xff1a;大模型能否在手机这样的移动设备上运行。过去&#xff0c;动辄数十亿参数的大模型似乎与手机绝缘&#xff0c;但如今&#xff0c;通过一系列前沿的量化、压缩和推理优化技术&#xff0c;将大模型“塞进”手机已不再是天方夜谭。这背…

作者头像 李华
网站建设 2026/8/8 9:57:20

如何打造专业高效的医疗科技网站建设方案并实现流量与转化的双重突破

在这个数字化浪潮席卷全球的时代,医疗行业正经历着一场前所未有的变革。作为一名在网页设计和开发领域摸爬滚打多年的从业者,我亲眼见证了太多原本在行业里深耕多年的医疗机构,因为不懂互联网,逐渐被那些擅长流量运营的新生力量挤压生存空间。今天,我想抛开那些晦涩难懂的…

作者头像 李华
网站建设 2026/8/8 9:51:30

[具身智能-796]:直流电机的信号死区?并图示

直流电机信号死区&#xff08;控制死区&#xff09;概念&#xff1a;控制器输出 PWM 控制指令已经不为 0&#xff0c;但电机实际转速 / 输出转矩仍然为 0的输入区间&#xff0c;属于非线性环节。 ❗区分&#xff1a;信号死区&#xff08;控制死区&#xff09;&#xff1a;指令‑…

作者头像 李华
网站建设 2026/8/8 9:49:10

逆向工程实战:手动重建Enigma Protector加壳DLL的导入表

1. 项目概述&#xff1a;为什么我们要深入Enigma Protector的加载器 如果你在逆向分析或安全研究领域摸爬滚打过一段时间&#xff0c;大概率遇到过被Enigma Protector加壳保护的Windows程序或DLL。它就像一个“黑盒”&#xff0c;把原始代码封装起来&#xff0c;运行时再动态解…

作者头像 李华