带子菜单的IExplorerCommand右键菜单经典模式下目录背景点击IShellItemArray为空
问题:经典上下文菜单中带子菜单的IExplorerCommand点击目录背景时IShellItemArray为空
基于IExplorerCommand实现的上下文菜单处理程序,在Windows 11现代和经典上下文菜单中均可正常显示带子菜单的右键菜单,说明MSIX包和AppxManifest.xml配置无问题。
存在以下异常现象:
- 使用经典上下文菜单(Windows 10原生或Windows 11 Shift+右键唤起)点击目录背景时,
IShellItemArray为空;而Windows 11现代上下文菜单中可正常获取内容。 - 右键菜单无任何子菜单时,无论使用经典还是现代上下文菜单,点击目录背景后
IShellItemArray都会包含当前文件夹路径;仅当菜单包含一个或多个子菜单且使用经典上下文菜单时,才会出现IShellItemArray为空的情况。 - 右键点击文件或文件夹时,无论菜单有无子菜单,
IShellItemArray均能正常填充内容。
以下是可复现问题的极简示例代码:
#include "pch.h" #include <wrl/module.h> #include <shobjidl_core.h> #include <wil/resource.h> #include <vector> using namespace Microsoft::WRL; HMODULE g_hModule = nullptr; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hModule = hModule; break; case DLL_THREAD_ATTACH: </think_never_used_51bce0c785ca2f68081bfa7d91973934> case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } // If user right-clicks a file or folder, the selected item is in the array // If user right-clicks the background, the array is empty // Issue only occurs when using classic context menu class SubCommand : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand> { public: SubCommand(PCWSTR title, PCWSTR icon, PCWSTR command) : m_title(title), m_icon(icon), m_command(command) { } IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name) { *name = nullptr; auto title = wil::make_cotaskmem_string_nothrow(m_title); RETURN_IF_NULL_ALLOC(title); *name = title.release(); return S_OK; } IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* iconPath) { *iconPath = nullptr; return E_NOTIMPL; } IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip) { *infoTip = nullptr; return E_NOTIMPL; } IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName) { *guidCommandName = GUID_NULL; return S_OK; } IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState) { *cmdState = ECS_ENABLED; return S_OK; } IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept { try { DWORD count = 0; if (selection) { RETURN_IF_FAILED(selection->GetCount(&count)); } if (count > 0) { ComPtr<IShellItem> item; RETURN_IF_FAILED(selection->GetItemAt(0, &item)); PWSTR filePath; RETURN_IF_FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &filePath)); wil::unique_cotaskmem_string filePathCleanup(filePath); WCHAR message[100]; StringCchPrintfW(message, ARRAYSIZE(message), L"First item: %s", filePath); MessageBox(nullptr, message, L"Debug", MB_OK); } else { MessageBox(nullptr, L"No items selected", L"Debug", MB_OK); } return S_OK; } catch (...) { return E_FAIL; } } IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags) { *flags = ECF_DEFAULT; return S_OK; } IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands) { *enumCommands = nullptr; return E_NOTIMPL; } private: PCWSTR m_title; PCWSTR m_icon; PCWSTR m_command; }; class SubCommandEnum : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IEnumExplorerCommand> { public: SubCommandEnum() { m_commands.push_back(Make<SubCommand>(L"SubmenuTest", L"SubmenuTest.ico", L"SubmenuTest")); } IFACEMETHODIMP Next(ULONG celt, IExplorerCommand** pUICommand, ULONG* pceltFetched) { ULONG fetched = 0; while (fetched < celt && m_index < m_commands.size()) { pUICommand[fetched] = m_commands[m_index].Get(); pUICommand[fetched]->AddRef(); ++fetched; ++m_index; } if (pceltFetched) { *pceltFetched = fetched; } return (fetched == celt) ? S_OK : S_FALSE; } IFACEMETHODIMP Skip(ULONG celt) { m_index += celt; return (m_index < m_commands.size()) ? S_OK : S_FALSE; } IFACEMETHODIMP Reset() { m_index = 0; return S_OK; } IFACEMETHODIMP Clone(IEnumExplorerCommand** ppenum) { auto clone = Make<SubCommandEnum>(); clone->m_index = m_index; *ppenum = clone.Detach(); return S_OK; } private: std::vector<ComPtr<IExplorerCommand>> m_commands; size_t m_index = 0; }; class __declspec(uuid("8A0E292E-E857-424B-9457-EC5377D0DCFD")) ContextMenuHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite> { public: IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name) { *name = nullptr; auto title = wil::make_cotaskmem_string_nothrow(L"ExplorerHelper"); RETURN_IF_NULL_ALLOC(title); *name = title.release(); return S_OK; } IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* iconPath) { *iconPath = nullptr; return E_NOTIMPL; } IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip) { *infoTip = nullptr; return E_NOTIMPL; } IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName) { *guidCommandName = GUID_NULL; return S_OK; } IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState) { *cmdState = ECS_ENABLED; return S_OK; } IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept { // Any code added here no longer runs now that we have a submenu return S_OK; } IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags) { *flags = ECF_HASSUBCOMMANDS; return S_OK; } IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands) { *enumCommands = Make<SubCommandEnum>().Detach(); return S_OK; } IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept { m_site = site; return S_OK; } IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); } protected: ComPtr<IUnknown> m_site; }; CoCreatableClass(ContextMenuHandler) STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory) { return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory); } _Use_decl_annotations_ STDAPI DllCanUnloadNow(void) { return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE; } _Use_decl_annotations_ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** instance) { return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance); }
内容的提问来源于stack exchange,提问作者LesFerch
相关产品推荐
相关产品推荐

