news 2026/8/8 10:28:34

windows快捷菜单(简化菜单/新版菜单/IExplorerCommand)开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
windows快捷菜单(简化菜单/新版菜单/IExplorerCommand)开发指南

一、前言

win11更新后微软对右键菜单进行了重新设计,分为了新版右键菜单(简化菜单/快捷菜单)和旧版菜单(win10菜单)。而新版菜单和旧版菜单的设计有巨大差别,无法按照原本旧版菜单的方法(包括注册表直接改写、shellex程序等方式)。

二、简述

win11新版菜单需要两组项目,需要实现IExplorerCommand接口的组件,并将其编写msix的Package.appxmanifest使其注册对应的到右键菜单上并进行打包安装:

  • 实现IExplorerCommand接口的右键拓展组件
  • 通过VS的 使用 Visual Studio 从源代码打包桌面应用 - MSIX | Microsoft Learn 或 MSIX 打包工具概述 - MSIX | Microsoft Learn 将组件添加入安装资源并改写其中的Package.appxmanifest 文件使其正确被注册(msix的虚拟注册表)并添加到合适的Type中。打包成对应的MSIX安装包进行安装(建议虚拟机/沙盒环境测试,不合格将自动闪退)。


而对于旧版win10菜单,首先实现IContextMenu的右键拓展程序组件,之后可以regsvr32程序直接注册(msix项目不建议)到注册表中的<file_extension> Key (包括HKCR下的‘*’ 和 各后缀名、文件、文件夹及背景等)的指定位置。也可以类似win11,使用Package.appxmanifest 文件FileExplorerClassicContextMenuHandler标签(要求高版本)将组件注册到虚拟注册表中。

两者配合实现新旧菜单同时使用。

开发要求:

对于win11右键菜单:
- 开发环境:推荐使用windows系统环境下使用VisualStudio2022+的IDE
- VisualStudio负载:C++的桌面开发、.NET桌面开发
- 开发项目模板:ATL项目(支持包括IExplorerCommand接口在内的win32程序开发)、windows应用程序打包项目、空项目(用于处理应用程序打包项目的入口程序(可选))
对于win10右键菜单
- 开发环境:推荐使用windows系统环境下使用VisualStudio2017+的IDE
- VisualStudio负载:C++的桌面开发
- 开发项目模板:ATL项目

三、开发指南

1、准备项目

需要创建一个空白解决方案用来收拢子项目,再依次创建ATL项目和windows应用程序打包项目、空项目(如果没有exe程序需求可以直接int main(){return 0;}

1、空白解决方案

2、创建子项目

3、创建ATL项目

配置新项目,细节可参考 windows ATL项目简介-CSDN博客

4、创建windows应用程序打包项目

配置版本,细节可参考 Windows 10中有什么不同 - Windows UWP applications | Microsoft Learn

2、配置ATL项目

创建ATL项目后,需要创建ATL模块来专门为接口实现注册等:

在添加新项中选择ATL->ATL简单对象,命名后确认下一步,一般无需改动直接下一步到完成,其他细节可参考 windows ATL项目简介-CSDN博客

IContextMenu和IExplorerCommand具体实现后文陈述。

3、配置windows应用程序打包项目

  • 添加资源:直接在 解决方案资源管理器内右键->添加->现有项 将上文的dll等资源(成品)添加入项目中。
  • 添加入口程序:右击依赖项项目引用 将exe程序(可以是前面的空项目做的简单程序)引入,必须存在,否则打包程序无法运行。
  • 配置Package.appxmanifest 文件,可参考应用包清单 - Windows UWP applications | Microsoft Learn ,此为示例:其中com命名空间用来注册组件本身、FileExplorerContextMenus用来注册到新版快捷菜单、FileExplorerClassicContextMenuHandler来支持旧版菜单(需求版本高,低版本应直接调用regsvr32注册程序),

    以下为示例(有瑕疵需根据情况修正)

<?xml version="1.0" encoding="utf-8"?> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop9="http://schemas.microsoft.com/appx/manifest/desktop/windows10/9" xmlns:desktop10="http://schemas.microsoft.com/appx/manifest/desktop/windows10/10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18" IgnorableNamespaces="uap rescap desktop4 desktop9 uap18 com"> <Identity Name="9ae0e8eb-306d-4b54-bb9c-fd987a602a57" Publisher="CN=devptx" Version="1.0.13.0" /> <Properties> <DisplayName>WapProjTemplate1</DisplayName> <PublisherDisplayName>Dallas</PublisherDisplayName> <Logo>Images\StoreLogo.png</Logo> </Properties> <Dependencies> <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" /> <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" /> </Dependencies> <Resources> <Resource Language="x-generate"/> </Resources> <Applications> <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$" uap18:RuntimeBehavior="win32App"> <uap:VisualElements DisplayName="WapProjTemplate1" Description="WapProjTemplate1" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png"> <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" /> <uap:SplashScreen Image="Images\SplashScreen.png" /> </uap:VisualElements> <Extensions> <com:Extension Category="windows.comServer"> <com:ComServer> <com:SurrogateServer DisplayName="DllReg Extensions"> <com:Class Id="9dd00a4a-9701-4c02-813c-2fd842b33587" Path="DllRegExt.dll" ThreadingModel="STA" /> <com:Class Id="6f3d9fe2-7d96-4910-9d34-e7e57378bc64" Path="Sample.dll" ThreadingModel="STA" /> </com:SurrogateServer> </com:ComServer> </com:Extension> <desktop4:Extension Category="windows.fileExplorerContextMenus"> <desktop4:FileExplorerContextMenus> <desktop4:ItemType Type="*"> <desktop4:Verb Id="regs" Clsid="9dd00a4a-9701-4c02-813c-2fd842b33587" /> <desktop4:Verb Id="testATL" Clsid="6f3d9fe2-7d96-4910-9d34-e7e57378bc64" /> </desktop4:ItemType> </desktop4:FileExplorerContextMenus> </desktop4:Extension> <desktop9:Extension Category="windows.fileExplorerClassicContextMenuHandler"> <desktop9:FileExplorerClassicContextMenuHandler> <desktop9:ExtensionHandler Type="*" Clsid="9dd00a4a-9701-4c02-813c-2fd842b33587" /> <!--desktop9:ExtensionHandler Type="*" Clsid="6f3d9fe2-7d96-4910-9d34-e7e57378bc64" /--> </desktop9:FileExplorerClassicContextMenuHandler> </desktop9:Extension> </Extensions> </Application> </Applications> <Capabilities> <rescap:Capability Name="runFullTrust" /> </Capabilities> </Package>

四、实现上下文菜单扩展

具体参考案例可参考lan2405/staticcontextmenu

参考代码:
ATLSimpleObject.h

// ATLSimpleObject.h: CATLSimpleObject 的声明 #pragma once #include "resource.h" // 主符号 #include <ShObjIdl_core.h> #include <string> #include "Sample_i.h" #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA) #error "Windows CE 平台(如不提供完全 DCOM 支持的 Windows Mobile 平台)上无法正确支持单线程 COM 对象。定义 _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制 ATL 支持创建单线程 COM 对象实现并允许使用其单线程 COM 对象实现。rgs 文件中的线程模型已被设置为“Free”,原因是该模型是非 DCOM Windows CE 平台支持的唯一线程模型。" #endif using namespace ATL; // CATLSimpleObject class ATL_NO_VTABLE CATLSimpleObject : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CATLSimpleObject, &CLSID_ATLSimpleObject>, public IDispatchImpl<IATLSimpleObject, &IID_IATLSimpleObject, &LIBID_SampleLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, public IShellExtInit, public IContextMenu, public IExplorerCommand { public: CATLSimpleObject() { } DECLARE_REGISTRY_RESOURCEID(106) BEGIN_COM_MAP(CATLSimpleObject) COM_INTERFACE_ENTRY(IATLSimpleObject) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IShellExtInit) COM_INTERFACE_ENTRY(IContextMenu) COM_INTERFACE_ENTRY(IExplorerCommand) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: // IShellExtInit STDMETHOD(Initialize)(_In_ LPCITEMIDLIST pidlFolder, _In_ IDataObject* pdtobj, _In_ HKEY hkeyProgID) override; // IContextMenu STDMETHOD(QueryContextMenu)(_In_ HMENU hMenu, _In_ UINT indexMenu, _In_ UINT idCmdFirst, _In_ UINT idCmdLast, _In_ UINT uFlags) override; STDMETHOD(InvokeCommand)(_In_ LPCMINVOKECOMMANDINFO pici) override; STDMETHOD(GetCommandString)(_In_ UINT_PTR idCmd, _In_ UINT uFlags, _Out_opt_ UINT* pwReserved, _Out_writes_opt_(cchOut) LPSTR pszName, _In_ UINT cchOut) { return S_OK; }; // IExplorerCommand STDMETHOD(EnumSubCommands)(_Out_ IEnumExplorerCommand** ppEnum) override; STDMETHOD(GetCanonicalName)(_Out_ GUID* pguidCommandName) override; STDMETHOD(GetFlags)(_Out_ EXPCMDFLAGS* pFlags) override; STDMETHOD(GetIcon)(_In_ IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszIcon) override; STDMETHOD(GetState)(_In_ IShellItemArray* psiItemArray, _In_ BOOL fOkToBeSlow, _Out_ EXPCMDSTATE* pCmdState) override; STDMETHOD(GetTitle)(_In_ IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszName) override; STDMETHOD(GetToolTip)(_In_ IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszInfotip) override; STDMETHOD(Invoke)(_In_ IShellItemArray* psiItemArray, _Out_ IBindCtx* pbc) override; }; OBJECT_ENTRY_AUTO(__uuidof(ATLSimpleObject), CATLSimpleObject)

ATLSimpleObject.cpp

// ATLSimpleObject.cpp: CATLSimpleObject 的实现 #include "pch.h" #include "ATLSimpleObject.h" // CATLSimpleObject // IShellExtInit STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::Initialize(LPCITEMIDLIST pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) { return S_OK; } // IContextMenu STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { HMENU uhmenu = CreatePopupMenu(); MENUITEMINFO hmenu_lpmi{ sizeof(MENUITEMINFO),MIIM_TYPE | MIIM_STATE | MIIM_SUBMENU,MFT_STRING,MFS_ENABLED }; hmenu_lpmi.cch = idCmdFirst; hmenu_lpmi.hSubMenu = uhmenu; hmenu_lpmi.dwTypeData = _T("注册dlltest"); MENUITEMINFO uhmenu_lpmi0{ sizeof(MENUITEMINFO),MIIM_TYPE | MIIM_STATE | MIIM_ID ,MFT_STRING,MFS_ENABLED ,idCmdFirst + 1 }; MENUITEMINFO uhmenu_lpmi1{ sizeof(MENUITEMINFO),MIIM_TYPE | MIIM_STATE | MIIM_ID ,MFT_STRING,MFS_ENABLED, idCmdFirst + 2 }; uhmenu_lpmi0.dwTypeData = _T("注册test"); uhmenu_lpmi1.dwTypeData = _T("注销test"); InsertMenuItem(uhmenu, idCmdFirst + 1, TRUE, (LPCMENUITEMINFO)&uhmenu_lpmi0); InsertMenuItem(uhmenu, idCmdFirst + 2, TRUE, (LPCMENUITEMINFO)&uhmenu_lpmi1); InsertMenuItem(hMenu, indexMenu, TRUE, (LPCMENUITEMINFO)&hmenu_lpmi); return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 2); } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { return E_NOTIMPL; } //STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchOut) //{ // return E_NOTIMPL; //} // IExplorerCommand STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::EnumSubCommands(_Out_ IEnumExplorerCommand** ppEnum) { *ppEnum = nullptr; return E_NOTIMPL; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetCanonicalName(_Out_ GUID* pguidCommandName) { *pguidCommandName = GetObjectCLSID(); return S_OK; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetFlags(_Out_ EXPCMDFLAGS* pFlags) { *pFlags = ECF_DEFAULT; return S_OK; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetIcon(IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszIcon) { return E_NOTIMPL; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, _Out_ EXPCMDSTATE* pCmdState) { *pCmdState = ECS_ENABLED; return S_OK; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetTitle(IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszName) { std::wstring titlename =_T( "test"); return SHStrDup(titlename.c_str(), ppszName); } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::GetToolTip(IShellItemArray* psiItemArray, _Out_ LPWSTR* ppszInfotip) { *ppszInfotip = nullptr; return E_NOTIMPL; } STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::Invoke(IShellItemArray* psiItemArray, _Out_ IBindCtx* pbc) { return E_NOTIMPL; }

多级菜单

与Package.appxmanifest 相关:

<com:Extension Category="windows.comServer"> <com:ComServer> <com:SurrogateServer DisplayName="DllReg Extensions"> <com:Class Id="9dd00a4a-9701-4c02-813c-2fd842b33587" Path="DllRegExt.dll" ThreadingModel="STA" /> <com:Class Id="6f3d9fe2-7d96-4910-9d34-e7e57378bc64" Path="Sample.dll" ThreadingModel="STA" /> </com:SurrogateServer> </com:ComServer> </com:Extension> <desktop4:Extension Category="windows.fileExplorerContextMenus"> <desktop4:FileExplorerContextMenus> <desktop4:ItemType Type="*"> <desktop4:Verb Id="regs" Clsid="9dd00a4a-9701-4c02-813c-2fd842b33587" /> <desktop4:Verb Id="testATL" Clsid="6f3d9fe2-7d96-4910-9d34-e7e57378bc64" /> </desktop4:ItemType> </desktop4:FileExplorerContextMenus> </desktop4:Extension>

代表id为regs和testATL(与菜单显示文字无关,实际内容由其IExplorerCommand决定)两个子菜单并存:

生成MSIX程序包

右键打包项目->发布->创建应用程序包,配置后即可创建
ps.为了能够在其他环境中能正常测试,建议直接使用个人测试证书(可直接生成),微软商店发布不需要证书,此外渠道自主发布需要签名证书,如阿里云EV证书(1000+r)


六、测试

生成后在项目文件夹的\WapProjTemplate1\AppPackages存储生成的安装包, 执行Add-AppDevPackage.ps1脚本按照测试证书安装(自动安装测试证书)。


注意事项

1、 使用个人测试证书需要主动开启开发人员模式
2、报错cannot be loaded because running scripts is disabled on this system.:
Windows PowerShell 默认的安全策略禁止运行脚本,需要执行
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

参考资料

  1. 微软文档-快捷菜单(上下文)菜单和快捷菜单处理程序 - Win32 apps | Microsoft Learn
  2. 微软文档-Windows应用程序开发 - 最佳做法 - Windows apps | Microsoft Learn
  3. 微软文档-[应用程序打包项目](使用 Visual Studio 从源代码打包桌面应用 - MSIX | Microsoft Learn)
  4. 微软文档-[MSIX Packaging Tool](MSIX 打包工具概述 - MSIX | Microsoft Learn)
  5. 微软文档-Windows 10中有什么不同 - Windows UWP applications | Microsoft Learn
  6. 微软文档-IExplorerCommand (shobjidl_core.h) - Win32 apps | Microsoft Learn
  7. 微软文档-打包应用 - UWP applications | Microsoft Learn
  8. 微软文档-desktop4:FileExplorerContextMenus - Windows UWP applications | Microsoft Learn
  9. 微软文档-应用包清单 - Windows UWP applications | Microsoft Learn ,
  10. CSDN-注册表常见位置以及COM注册表项架构详解_右键菜单注册表-CSDN博客
  11. CSDN-windows ATL项目简介-CSDN博客
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 10:23:40

又一次面试整理

这次是个新开的公司&#xff0c;不得不说其实深圳的公司招人都挺奇怪的&#xff0c;流程乱七八糟的&#xff0c;没有上海那边那么好 1.Android id 是什么 2.现在有个传感器&#xff0c;你应该怎么适配 3.模拟teamview &#xff0c;你觉得应该怎么实现 Android ID 是 SettingsPr…

作者头像 李华
网站建设 2026/8/8 10:23:06

IP组播完全入门指南(从零到HCIP)

&#x1f4da; 前置知识&#xff08;先确认你会这些&#xff09;知识点你需要知道IP地址分类A/B/C/D/E类地址的区别MAC地址48位二层地址&#xff0c;以太网帧使用单播路由路由器如何查表转发数据包子网/网段什么是广播域、什么是路由器隔离基本命令行华为VRP系统的基本操作 如果…

作者头像 李华
网站建设 2026/8/8 10:22:37

戴尔网站建设的目标:如何打造符合企业长期发展的专业数字门户

在这个数字化浪潮汹涌澎湃的时代,企业不再仅仅依赖传统的线下门店或电话销售,互联网已经成为连接客户、展示品牌、转化业务的核心阵地。对于像戴尔这样拥有深厚硬件制造背景和全球化视野的企业来说,网站建设绝不仅仅是放几张产品图片那么简单,它更像是一个精密运转的数字中…

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

SpringBoot图书馆座位管理系统设计与实现

1. 项目概述&#xff1a;图书馆座位预订管理系统的核心价值 图书馆座位资源紧张是高校和公共图书馆长期存在的痛点。每到考试季或周末&#xff0c;学生们凌晨排队抢座位的场景屡见不鲜。这个基于SpringBoot的座位管理系统&#xff0c;正是为了解决这一现实问题而设计的数字化解…

作者头像 李华
网站建设 2026/8/8 10:14:27

宣城网站建设jidela 揭秘:如何让本地中小企业的官网不再是“摆设”,真正带来业务增长?

做企业这么多年,我见过太多老板在网站上投入了真金白银,结果最后发现,那页面不仅没带来一个客户,反而成了企业形象的“绊脚石”。尤其是在宣城这样的城市,很多老板觉得“反正本地人就找熟人”,对网站这档子事不屑一顾。但今天我想和大家掏心窝子说句实话,在这个互联网无…

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

RAG与LoRA技术融合:构建希腊语多领域专家模型的实战指南

在实际的自然语言处理项目中&#xff0c;我们常常面临一个核心挑战&#xff1a;如何让一个通用的大语言模型&#xff08;LLM&#xff09;精通一门特定语言&#xff08;如希腊语&#xff09;并深入理解多个专业领域&#xff08;如法律、医学、科技&#xff09;的术语和知识。直接…

作者头像 李华