云计算百科
云计算领域专业知识百科平台

windows 多级右键菜单(新版菜单IExplorerCommand和旧版菜单IContextMenu)

目录

IContextMenu多级右键菜单

简述

结构

案例

IExplorerCommand多级菜单

简述

结构

方法函数详解

方法函数

方法详解

常用方法案例(便于直接复制使用)

案例

文件结构

IExCmdMainMenus.h

IExCmdSubMenus.h

IExCmdSubMenus.cpp


IContextMenu多级右键菜单

简述

IContextMenu(旧版菜单)的右键菜单依赖于QueryContextMenu,通常使用CreatePopupMenu()和InsertMenuItem()来添加子菜单,之后通过InvokeCommand的lpici的高低位来筛选哪个菜单的响应来响应菜单。

结构

QueryContextMenu(HMENU,UINT,UINT,UINT,UINT);
|
InsertMenuItem
|
CreatePopupMenu
|
InsertMenuItem
InsertMenuItem
InsertMenuItem

InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
||
HIWORD(lpici->lpVerb)LOWORD(lpici->lpVerb)
||
谓词字符串调用 通过命令ID调用
||
commandcommand

案例

QueryContextMenu

STDMETHODIMP_(HRESULT __stdcall) CDllRegEx::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
bool bAllDll = true;
// 遍历确认文件是不是.dll程序
for (const auto& path : m_selectedFiles)
{
const TCHAR* ext = PathFindExtension(path.c_str());
if (ext && _wcsicmp(ext, _T(".dll")) != 0)
{
bAllDll = false;
break;
}
}
// 如果有不为dll的则此拓展不生效
if (!bAllDll)
{
return E_FAIL;
}
initRegMenu(hmenu, indexMenu, idCmdFirst);
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, CMD_END + 1);;
}
void CDllRegEx::initRegMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst)
{
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("注册dll");

MENUITEMINFO uhmenu_lpmi0{ sizeof(MENUITEMINFO),MIIM_TYPE | MIIM_STATE | MIIM_ID ,MFT_STRING,MFS_ENABLED ,idCmdFirst + CMD_REG };
MENUITEMINFO uhmenu_lpmi1{ sizeof(MENUITEMINFO),MIIM_TYPE | MIIM_STATE | MIIM_ID ,MFT_STRING,MFS_ENABLED, idCmdFirst + CMD_UREG };
uhmenu_lpmi0.dwTypeData = _T("注册");
uhmenu_lpmi1.dwTypeData = _T("注销");
InsertMenuItem(uhmenu, idCmdFirst + CMD_REG, TRUE, (LPCMENUITEMINFO)&uhmenu_lpmi0);
InsertMenuItem(uhmenu, idCmdFirst + CMD_UREG, TRUE, (LPCMENUITEMINFO)&uhmenu_lpmi1);

InsertMenuItem(hmenu, indexMenu, TRUE, (LPCMENUITEMINFO)&hmenu_lpmi);
}

InvokeCommand

STDMETHODIMP_(HRESULT __stdcall) CDllRegEx::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
{
// 1. 基本参数验证
if (!lpici)
return E_INVALIDARG;

// 2. 检查是否通过谓词字符串调用
if (HIWORD(lpici->lpVerb) != 0)
{
// 根据fMask判断使用ANSI还是Unicode
if (lpici->fMask & CMIC_MASK_UNICODE)
{
if (wcscmp((LPCWSTR)lpici->lpVerb, L"注册") == 0)
{
RegCommand(CMD_REG);
return S_OK;
}
else if (wcscmp((LPCWSTR)lpici->lpVerb, L"注销") == 0)
{
RegCommand(CMD_UREG);
return S_OK;
}
}
else
{
if (lstrcmpA((LPCSTR)lpici->lpVerb, "注册") == 0)
{
RegCommand(CMD_REG);
return S_OK;
}
else if (lstrcmpA((LPCSTR)lpici->lpVerb, "注销") == 0)
{
RegCommand(CMD_UREG);
return S_OK;
}
}

// 字符串不匹配时明确返回错误
return E_INVALIDARG;
}
// 3. 检查是否通过命令ID调用
else
{
switch (LOWORD(lpici->lpVerb))
{
case CMD_REG:
RegCommand(CMD_REG);
return S_OK;
case CMD_UREG:
RegCommand(CMD_UREG);
return S_OK;
default:
return E_INVALIDARG;
}
}
}

IExplorerCommand多级菜单

简述

上文: windows快捷菜单(简化菜单/新版菜单/IExplorerCommand)开发指南-CSDN博客 IExplorerCommand本身无法像IContextMenu一样直接实现多级菜单功能,但存在EnumSubCommands方法来传递IEnumExplorerCommand来间接实现。

结构

// IExplorerCommand
STDMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum)
|
V
// IEnumExplorerCommand
STDMETHOD(Next)(ULONG celt, IExplorerCommand** pUICommand, ULONG* pceltFetched)
|
V
子IExplorerCommand

方法函数详解

方法函数

IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override;
IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName) override;
IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags) override;
IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override;
IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override;
IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName) override;
IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override;
IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override;

方法详解

/**
* 用于配置子命令集
* @param ppEnum _out_ IEnumExplorerCommand** 返回包含可用于遍历子命令集
* @return HRESULT
*/
HRESULT EnumSubCommands(
IEnumExplorerCommand** ppEnum
);
/**
* 检索 当前命令的 GUID
* @param pguidCommandName [out] 当此方法成功返回时,将接收命令的 GUID,该 GUID 在注册表中声明它
* @return S_OK
*/
HRESULT GetCanonicalName(
GUID* pguidCommandName
);
/**
*获取与指定的 Windows 资源管理器命令项关联的标志。
*@param pFlags [out] 此方法返回时,此值指向当前命令标志。
ECF_DEFAULT (0x000)默认值
ECF_HASSUBCOMMANDS (0x001)子命令
ECF_HASSPLITBUTTON (0x002)拆分按钮
ECF_HIDELABEL (0x004)隐藏按钮
ECF_ISSEPARATOR (0x008)命令是分隔符。
ECF_HASLUASHIELD (0x010)显示 UAC 防护板。
ECF_SEPARATORBEFORE (0x020)命令位于紧邻分隔符下方的菜单中。
ECF_SEPARATORAFTER (0x040)命令位于紧靠分隔符上方的菜单中。
ECF_ISDROPDOWN (0x080)选择命令会打开一个下拉子菜单, (例如“ 包含在库) 中”。
ECF_TOGGLEABLE (0x100)
ECF_AUTOMENUICONS (0x200)
*/
HRESULT GetFlags(
EXPCMDFLAGS* pFlags
);
/**
* 返回该命令项的图标路径
* @param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针
* @param ppszIcon _out_ LPWSTR* 接收指向包含图标路径的字符串的指针。
* @return
*/
HRESULT GetIcon(
IShellItemArray* psiItemArray,
LPWSTR* ppszIcon
);
/**
* 返回该命令项的状态
* @param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针
* @param fOkToBeSlow _In_ BOOL 指示是否可以等待。
* @param pCmdState _Out_ EXPCMDSTATE*
ECS_ENABLED = 0, // 该项已启用
ECS_DISABLED = 0x1, // 灰显且不可访问的项
ECS_HIDDEN = 0x2, // 隐藏状态
ECS_CHECKBOX = 0x4, // 检查框显示
ECS_CHECKED = 0x8, // 带有检查框的项,并选中该检查框
ECS_RADIOCHECK = 0x10 // 项是通过单选按钮选择的一组互斥选项之一。
* @return
*/
HRESULT GetState(
IShellItemArray* psiItemArray,
BOOL fOkToBeSlow,
EXPCMDSTATE* pCmdState
);
/**
* 提供该命令项的名称
* @param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针
* @param ppszName _Out_ LPWSTR* 获取指向包含命令项名称的字符串的指针。
* @return
*/
HRESULT GetTitle(
IShellItemArray* psiItemArray,
LPWSTR* ppszName
);
/**
* 提供该命令项的提示
* @param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针
* @param ppszInfotip _Out_ LPWSTR* 获取指向包含命令项信息提示的字符串的指针。
* @return
*/
HRESULT GetToolTip(
IShellItemArray* psiItemArray,
LPWSTR* ppszInfotip
);
/**
* 命令函数
* @param psiItemArray _In_ IShellItemArray* 你的代码通过它获取"操作对象是谁"
psiItemArray->EnumItems(&pEnum)//<获取 IEnumShellItems 枚举器(包括每个文件的信息)
pEnum->Clone()
pEnum->Next()
pEnum->Reset()
pEnum->Skip()
psiItemArray->GetAttributes();//<获取项集的属性标志
psiItemArray->GetCount(&a);//<获取对象数量
psiItemArray->GetItemAt(0, &psi);//<获取索引处的项
psi->Compare()
psi->GetAttributes()
psi->GetDisplayName()
psi->GetParent()
psiItemArray->GetPropertyDescriptionList(); //<属性描述列表
psiItemArray->GetPropertyStore();//<属性存储区
* @param pbc _In_ IBindCtx* 提供"在什么上下文/条件下操作"
通常可为 nullptr,大部分场景不需要使用
pbc->SetBindOptions();//<设置绑定选项
pbc->GetBindOptions();//<获取绑定选项

pbc->EnumObjectParam();//<枚举对象参数
pbc->RegisterObjectParam();//<注册对象参数
pbc->GetObjectParam();//<获取对象参数
pbc->RevokeObjectParam();//<撤销对象参数

pbc->RegisterObjectBound();//<注册对象绑定
pbc->RevokeObjectBound();//<撤销对象绑定
pbc->ReleaseBoundObjects();//<释放绑定对象

pbc->GetRunningObjectTable();//<获取运行对象表
* @return
*/
HRESULT Invoke(
IShellItemArray* psiItemArray,
IBindCtx* pbc
);

常用方法案例(便于直接复制使用)

// 模板
IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override{*ppEnum = nullptr; return E_NOTIMPL;}
IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName) {*pguidCommandName=GetObjectCLSID();return S_OK;}
IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags){*pFlags=ECF_DEFAULT;return S_OK;}
IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon){return SHStrDup(_T("Resource.dll,201"), ppszIcon);}
IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState){*pCmdState=ECS_ENABLED;return S_OK;}
IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName){std::wstring titlename=_T( "test");return SHStrDup(titlename.c_str(), ppszName);}
IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip){std::wstring tooltip=_T( "test");return SHStrDup(tooltip.c_str(), ppszInfotip);}
IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override{return E_NOTIMPL;}

EnumSubCommands

IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override
{
if (!ppEnum)
return E_POINTER;

m_currentIndex = 0; // 重置枚举位置
this->AddRef(); // 返回前增加引用计数
*ppEnum = static_cast<IEnumExplorerCommand*>(this);
return S_OK;
}

Clone

IFACEMETHOD(Clone)(IEnumExplorerCommand** ppenum) override
{
*ppenum = NULL;
CComObject<CIExCmdMainMenus>* pNew;
HRESULT hr = CComObject<CIExCmdMainMenus>::CreateInstance(&pNew);
if (SUCCEEDED(hr)) {
pNew->AddRef();
pNew->m_currentIndex = m_currentIndex;
pNew->m_subCommands = m_subCommands; // 共享子命令
*ppenum = static_cast<IEnumExplorerCommand*>(pNew);
}
return hr;
}

Next

IFACEMETHOD(Next)(ULONG celt, IExplorerCommand** pUICommand, ULONG* pceltFetched) override
{
if (!pUICommand)
return E_POINTER;
ULONG fetched = 0;
while (fetched < celt && m_currentIndex < m_subCommands.size()) {
pUICommand[fetched] = m_subCommands[m_currentIndex];
pUICommand[fetched]->AddRef(); // 必须增加引用计数
m_currentIndex++;
fetched++;
}
if (pceltFetched) *pceltFetched = fetched;
return (fetched == celt) ? S_OK : S_FALSE;
}

Invoke

#include <windows.h>
#include <shobjidl_core.h>
#include <shlobj.h>
#include <shellapi.h>
#include <atlbase.h>
#include <string>
#include <vector>
#include <propvarutil.h>
#include <propkey.h>

// ============================================================================
// IExplorerCommand::Invoke 完整实现
// ============================================================================
STDMETHODIMP CMyExplorerCommand::Invoke(
_In_ IShellItemArray* psiItemArray,
_In_ IBindCtx* pbc)
{
// ========================================================================
// 一、参数校验
// ========================================================================
if (!psiItemArray)
return E_INVALIDARG;

// ========================================================================
// 二、从 psiItemArray 获取选中项数量
// ========================================================================
DWORD dwCount = 0;
HRESULT hr = psiItemArray->GetCount(&dwCount);
if (FAILED(hr) || dwCount == 0)
return S_OK; // 没有选中项,静默返回

// ========================================================================
// 三、方式一:通过 GetItemAt 按索引遍历(推荐,简单直观)
// ========================================================================
std::vector<std::wstring> filePaths;
filePaths.reserve(dwCount);

for (DWORD i = 0; i < dwCount; ++i)
{
CComPtr<IShellItem> psi;
hr = psiItemArray->GetItemAt(i, &psi);
if (FAILED(hr) || !psi)
continue;

// —— 3.1 获取文件系统完整路径(最常用) ——
PWSTR pszPath = nullptr;
hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr) && pszPath)
{
filePaths.emplace_back(pszPath);
CoTaskMemFree(pszPath); // 必须释放!
}
else
{
// 非文件系统项(如虚拟文件夹),回退获取显示名称
PWSTR pszName = nullptr;
hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, &pszName);
if (SUCCEEDED(hr) && pszName)
{
filePaths.emplace_back(pszName);
CoTaskMemFree(pszName);
}
}

// —— 3.2 获取项的属性标志(判断是文件还是文件夹) ——
SFGAOF sfgaoAttrs = 0;
hr = psi->GetAttributes(
SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK,
&sfgaoAttrs);
if (SUCCEEDED(hr))
{
bool bIsFolder = (sfgaoAttrs & SFGAO_FOLDER) != 0;
bool bIsFileSystem = (sfgaoAttrs & SFGAO_FILESYSTEM) != 0;
bool bIsLink = (sfgaoAttrs & SFGAO_LINK) != 0;
// 可根据 bIsFolder / bIsFileSystem / bIsLink 做分支处理
}
}

// ========================================================================
// 四、方式二:通过 EnumItems 枚举器遍历(适合流式处理)
// ========================================================================
/*
CComPtr<IEnumShellItems> pEnum;
hr = psiItemArray->EnumItems(&pEnum);
if (SUCCEEDED(hr) && pEnum)
{
CComPtr<IShellItem> psi;
while (pEnum->Next(1, &psi, nullptr) == S_OK)
{
PWSTR pszPath = nullptr;
hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr) && pszPath)
{
// 处理 pszPath …
CoTaskMemFree(pszPath);
}
psi.Release(); // 释放当前项,准备获取下一项
}
// 枚举器其他方法:
// pEnum->Reset(); // 重置到开头,可重新遍历
// pEnum->Skip(n); // 跳过 n 项
// pEnum->Clone(&pEnum2); // 克隆枚举器(注意:部分实现不支持)
}
*/

// ========================================================================
// 五、方式三:批量获取属性标志(适合需要整体判断的场景)
// ========================================================================
/*
SFGAOF bulkAttrs = 0;
// SIATTRIBFLAGS_AND: 所有项都有的属性位才置1
// SIATTRIBFLAGS_OR: 任一项有的属性位就置1
// SIATTRIBFLAGS_APPCOMPAT: 兼容性模式
hr = psiItemArray->GetAttributes(
SIATTRIBFLAGS_AND,
SFGAO_FOLDER | SFGAO_FILESYSTEM,
&bulkAttrs);
if (SUCCEEDED(hr))
{
bool allFolders = (bulkAttrs & SFGAO_FOLDER) != 0;
// 如果所有选中项都是文件夹,执行文件夹专属逻辑
}
*/

// ========================================================================
// 六、方式四:通过属性存储获取文件元数据(高级用法)
// ========================================================================
/*
CComPtr<IPropertyStore> pPropStore;
hr = psiItemArray->GetPropertyStore(
GPS_DEFAULT, // 默认属性存储
IID_PPV_ARGS(&pPropStore));
if (SUCCEEDED(hr) && pPropStore)
{
PROPVARIANT var;
PropVariantInit(&var);
// 获取文件修改时间
hr = pPropStore->GetValue(PKEY_DateModified, &var);
if (SUCCEEDED(hr))
{
// 使用 var 中的值…
PropVariantClear(&var);
}
}
*/

// ========================================================================
// 七、处理 pbc(IBindCtx 绑定上下文)
// ========================================================================
// pbc 在大多数 Invoke 场景中为 nullptr,不需要处理。
// 以下展示 pbc 不为空时可做的操作:
if (pbc)
{
// —— 7.1 获取绑定选项 ——
BIND_OPTS bindOpts = {};
bindOpts.cbStruct = sizeof(BIND_OPTS);
hr = pbc->GetBindOptions(&bindOpts);
if (SUCCEEDED(hr))
{
// bindOpts.grfFlags – 绑定标志
// bindOpts.grfMode – 存储打开模式(STGM_READ 等)
// bindOpts.dwTickCountDeadline – 超时限制
}

// —— 7.2 读取自定义对象参数 ——
// Shell 可能通过 RegisterObjectParam 注册了上下文信息
CComPtr<IUnknown> pUnkParam;
hr = pbc->GetObjectParam(L"MyCustomParam", &pUnkParam);
if (SUCCEEDED(hr) && pUnkParam)
{
// 使用自定义参数…
}
}

// ========================================================================
// 八、执行实际业务操作(示例:用默认程序打开所有选中文件)
// ========================================================================
for (const auto& path : filePaths)
{
// ShellExecuteW 会自动使用文件关联的默认程序打开
HINSTANCE hInst = ShellExecuteW(
nullptr, // 无父窗口
L"open", // 操作类型
path.c_str(), // 文件路径
nullptr, // 无额外参数
nullptr, // 使用文件所在目录
SW_SHOWNORMAL // 正常显示窗口
);
// 返回值 > 32 表示成功
if ((INT_PTR)hInst <= 32)
{
// 处理错误…
}
}

return S_OK;
}

案例

通过右键快捷菜单(简化菜单/win11菜单)实现dll组件的注册与注销。

文件结构

// IExCmdMainMenus.h
// 父菜单
// 同时实现父 IExplorerCommand 与 IEnumExplorerCommand
// 预处理一个std::vector<CComPtr<CIExCmdSubMenus>>绑定子菜单作为IEnumExplorerCommand::Next的参数
// 父 IExplorerCommand 实现父菜单信息并通过IExplorerCommand::EnumSubCommands获取本身所拥有的IEnumExplorerCommand环境
// IEnumExplorerCommand

// IExCmdSubMenus.h
// 子菜单
// 实现子 IExplorerCommand
// 根据父菜单传递的菜单编号(可自定义)来针对性执行 Invoke

// IExCmdSubMenus.cpp
// 单独处理IExplorerCommand::Invoke

IExCmdMainMenus.h

// IExCmdMainMenus.h: CIExCmdMainMenus 的声明

#pragma once
#include <string>
#include <vector>
#include "resource.h" // 主符号

#include "DllRegIExCmdExt_i.h"
#include "IExCmdSubMenus.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;

// CIExCmdMainMenus

class ATL_NO_VTABLE CIExCmdMainMenus :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CIExCmdMainMenus, &CLSID_IExCmdMainMenus>,
public IDispatchImpl<IIExCmdMainMenus, &IID_IIExCmdMainMenus, &LIBID_DllRegIExCmdExtLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IExplorerCommand,
public IEnumExplorerCommand
{
public:
DECLARE_REGISTRY_RESOURCEID(106)
BEGIN_COM_MAP(CIExCmdMainMenus)
COM_INTERFACE_ENTRY(IIExCmdMainMenus)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IExplorerCommand)
COM_INTERFACE_ENTRY(IEnumExplorerCommand)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
m_subCommands.clear();
for (int i = 1; i < CMD_END; i++)
{

CComObject<CIExCmdSubMenus>* pRaw = nullptr;
HRESULT hr = CComObject<CIExCmdSubMenus>::CreateInstance(&pRaw);
if (FAILED(hr))
continue;

pRaw->set_m_size(i);
// CComPtr 构造函数会 AddRef,refcount: 0 → 1
CComPtr<CIExCmdSubMenus> pSubCmd(pRaw);
m_subCommands.push_back(pSubCmd);
}
return S_OK;
}

void FinalRelease()
{
m_subCommands.clear();
}

private:
size_t m_currentIndex = 0;
std::vector<CComPtr<CIExCmdSubMenus>> m_subCommands;
public:
// Init

// IExplorerCommand
IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override
{
if (!ppEnum)
return E_POINTER;

m_currentIndex = 0; // 重置枚举位置
this->AddRef(); // 返回前增加引用计数
*ppEnum = static_cast<IEnumExplorerCommand*>(this);
return S_OK;
}
IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName)override { *pguidCommandName = GetObjectCLSID(); return S_OK; }
IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags)override { *pFlags = ECF_HASSUBCOMMANDS; return S_OK; }
IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon)override { return SHStrDup(_T("Resource.dll,201"), ppszIcon); }
IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState)override { *pCmdState = ECS_ENABLED; return S_OK; }
IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName) override { std::wstring titlename = _T("IExCmd主注册"); return SHStrDup(titlename.c_str(), ppszName); }
IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip)override { std::wstring tooltip = _T("IExplorerCommand注册主程序"); return SHStrDup(tooltip.c_str(), ppszInfotip); }
IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override { return E_NOTIMPL; }
// IEnumExplorerCommand
IFACEMETHOD(Clone)(IEnumExplorerCommand** ppenum) override
{
*ppenum = NULL;
CComObject<CIExCmdMainMenus>* pNew;
HRESULT hr = CComObject<CIExCmdMainMenus>::CreateInstance(&pNew);
if (SUCCEEDED(hr)) {
pNew->AddRef();
pNew->m_currentIndex = m_currentIndex;
pNew->m_subCommands = m_subCommands; // 共享子命令
*ppenum = static_cast<IEnumExplorerCommand*>(pNew);
}
return hr;
}
IFACEMETHOD(Next)(ULONG celt, IExplorerCommand** pUICommand, ULONG* pceltFetched) override
{
if (!pUICommand)
return E_POINTER;
ULONG fetched = 0;
while (fetched < celt && m_currentIndex < m_subCommands.size()) {
pUICommand[fetched] = m_subCommands[m_currentIndex];
pUICommand[fetched]->AddRef(); // 必须增加引用计数
m_currentIndex++;
fetched++;
}
if (pceltFetched) *pceltFetched = fetched;
return (fetched == celt) ? S_OK : S_FALSE;
}
IFACEMETHOD(Reset)() override { m_currentIndex = 0; return S_OK; }
IFACEMETHOD(Skip)(ULONG celt) override { return E_NOTIMPL; }
// Private

};

OBJECT_ENTRY_AUTO(__uuidof(IExCmdMainMenus), CIExCmdMainMenus)

IExCmdSubMenus.h

// IExCmdSubMenus.h: CIExCmdSubMenus 的声明

#pragma once
#include <string>
#include <vector>
#include "resource.h" // 主符号
#include <ShlObj_core.h>
#include "DllRegIExCmdExt_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;

enum MyCmd {
CMD_ROOT = 0, ///< 根命令
CMD_REG, ///< 注册
CMD_UREG, ///< 反注册
CMD_END ///< 结束
};
struct ExplorerCommand {
GUID guid;
EXPCMDFLAGS flags; ///< GetFlags
const TCHAR* icon; ///< GetIcon
EXPCMDSTATE state; ///< GetState
const TCHAR* title; ///< GetTitle
const TCHAR* tooltip; ///< GetToolTip
};

const ExplorerCommand IExCmdInfoRegistry[CMD_END] = {
{
{ 0, 0, 0, {0,0,0,0,0,0,0,0} }, // guid
ECF_DEFAULT, // flags
nullptr, // icon
ECS_ENABLED, // state
nullptr, // title
nullptr // tooltip
},
{
// {5087E382-421C-487A-841E-4B219BDA0DD4}
{0x5087e382, 0x421c, 0x487a, { 0x84, 0x1e, 0x4b, 0x21, 0x9b, 0xda, 0xd, 0xd4 }},
ECF_DEFAULT,
nullptr,
ECS_ENABLED,
_T("注册组件"),
_T("注册组件")
},
// ── 注销组件 ──
{
// {39FB48D9-5E73-4533-B0C3-565857AFF622}
{ 0x39fb48d9, 0x5e73, 0x4533, { 0xb0, 0xc3, 0x56, 0x58, 0x57, 0xaf, 0xf6, 0x22 } },
ECF_DEFAULT,
_T(""),
ECS_ENABLED,
_T("注销组件"),
_T("注销组件")
},
};
// CIExCmdSubMenus

class ATL_NO_VTABLE CIExCmdSubMenus :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CIExCmdSubMenus, &CLSID_IExCmdSubMenus>,
public IDispatchImpl<IIExCmdSubMenus, &IID_IIExCmdSubMenus, &LIBID_DllRegIExCmdExtLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IExplorerCommand
{
public:
CIExCmdSubMenus()
{
}

DECLARE_REGISTRY_RESOURCEID(107)

BEGIN_COM_MAP(CIExCmdSubMenus)
COM_INTERFACE_ENTRY(IIExCmdSubMenus)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IExplorerCommand)
END_COM_MAP()

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{

return S_OK;
}

void FinalRelease()
{
}
private:
size_t m_size_=NULL;
public:
void set_m_size(size_t m_size) { m_size_ = m_size; }
// IExplorerCommand
IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override { *ppEnum = nullptr; return E_NOTIMPL; }
IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName) { *pguidCommandName =IExCmdInfoRegistry[m_size_].guid; return S_OK; }
IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags) { *pFlags = ECF_DEFAULT; return S_OK; }
IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) { return SHStrDup(IExCmdInfoRegistry[m_size_].icon, ppszIcon); }
IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) { *pCmdState = ECS_ENABLED; return S_OK; }
IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName) { std::wstring titlename = IExCmdInfoRegistry[m_size_].title; return SHStrDup(titlename.c_str(), ppszName); }
IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) { std::wstring tooltip = IExCmdInfoRegistry[m_size_].tooltip; return SHStrDup(tooltip.c_str(), ppszInfotip); }
IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override;

};

OBJECT_ENTRY_AUTO(__uuidof(IExCmdSubMenus), CIExCmdSubMenus)

IExCmdSubMenus.cpp

// IExCmdSubMenus.cpp: CIExCmdSubMenus 的实现

#include "pch.h"
#include "IExCmdSubMenus.h"

// CIExCmdSubMenus

IFACEMETHODIMP CIExCmdSubMenus::Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc)
{
// ========================================================================
// 一、参数校验
// ========================================================================
if (!psiItemArray)
return E_INVALIDARG;

// ========================================================================
// 二、从 psiItemArray 获取选中项数量
// ========================================================================
DWORD dwCount = 0;
HRESULT hr = psiItemArray->GetCount(&dwCount);
if (FAILED(hr) || dwCount == 0)
return S_OK; // 没有选中项,静默返回
// ========================================================================
// 三、方式一:通过 GetItemAt 按索引遍历(推荐,简单直观)
// ========================================================================
std::vector<std::wstring> filePaths;
filePaths.reserve(dwCount);

for (DWORD i = 0; i < dwCount; ++i)
{
CComPtr<IShellItem> psi;
hr = psiItemArray->GetItemAt(i, &psi);
if (FAILED(hr) || !psi)
continue;

// —— 3.1 获取文件系统完整路径(最常用) ——
PWSTR pszPath = nullptr;
hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr) && pszPath)
{
filePaths.emplace_back(pszPath);
CoTaskMemFree(pszPath); // 必须释放!
}
else
{
// 非文件系统项(如虚拟文件夹),回退获取显示名称
PWSTR pszName = nullptr;
hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, &pszName);
if (SUCCEEDED(hr) && pszName)
{
filePaths.emplace_back(pszName);
CoTaskMemFree(pszName);
}
}

// —— 3.2 获取项的属性标志(判断是文件还是文件夹) ——
SFGAOF sfgaoAttrs = 0;
hr = psi->GetAttributes(
SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK,
&sfgaoAttrs);
if (SUCCEEDED(hr))
{
bool bIsFolder = (sfgaoAttrs & SFGAO_FOLDER) != 0;
bool bIsFileSystem = (sfgaoAttrs & SFGAO_FILESYSTEM) != 0;
bool bIsLink = (sfgaoAttrs & SFGAO_LINK) != 0;
// 可根据 bIsFolder / bIsFileSystem / bIsLink 做分支处理
}
}
SHELLEXECUTEINFO RegCmdInfo;
std::wstring cmd;
for (const auto& path : filePaths)
{
cmd = (m_size_ == CMD_REG)
? L"\\"" + path + L"\\"" // 注册:仅传递DLL路径
: L"/u \\"" + path + L"\\""; // 反注册:添加 /u 参数
RegCmdInfo = {
sizeof(SHELLEXECUTEINFO),
SEE_MASK_DEFAULT,
nullptr,
_T("runas"),
_T("regsvr32.exe"),
cmd.c_str(),
NULL,
SW_SHOW,
nullptr,
nullptr,
nullptr,
nullptr
};
BOOL hInst = ShellExecuteEx(&RegCmdInfo);
if (!hInst)
{
return E_FAIL;
}
}

return S_OK;

}

赞(0)
未经允许不得转载:网硕互联帮助中心 » windows 多级右键菜单(新版菜单IExplorerCommand和旧版菜单IContextMenu)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!