使用IContextMenu::QueryContextMenu调用文件夹背景右键菜单失效问题
文件夹空白区域右键菜单InvokeCommand调用失败问题解决
问题描述
通过IContextMenu::QueryContextMenu获取文件夹空白区域的右键菜单,菜单可正常显示,但注册在HKEY_CLASSES_ROOT\Directory\Background\shell路径下的菜单项无法正常触发——调用InvokeCommand失败,错误码为-2147023741,错误提示为*“没有应用程序与此操作的指定文件关联”*。
临时解决方案
- 解析注册表中的命令
- 追踪选中命令的verb
- 若选中命令的verb与
HKEY_CLASSES_ROOT\Directory\Background\shell\verb\command中的项匹配,则手动执行该键记录的命令
期望实现
无需上述hack手段,让HKEY_CLASSES_ROOT\Directory\Background\shell路径下的命令通过InvokeCommand正常工作。
测试代码
运行时请将path变量替换为你的文件夹路径,点击“Show Context menu”按钮即可测试:
#include <windows.h> #include <shlobj.h> #include <atlbase.h> #include <strmif.h> #include <string> class Site : public IServiceProvider, public IFolderView, IOleWindow { HWND _hwnd; IShellItem* _item; public: Site(HWND hwnd, IShellItem* item) :_hwnd(hwnd), _item(item) {} // IUnknown ULONG AddRef() { return 1; } ULONG Release() { return 1; } HRESULT QueryInterface(REFIID riid, void** ppvObject) { if (riid == IID_IUnknown || riid == IID_IServiceProvider) { *ppvObject = static_cast<IServiceProvider*>(this); return S_OK; } if (riid == IID_IOleWindow) { *ppvObject = static_cast<IOleWindow*>(this); return S_OK; } if (riid == IID_IFolderView) { *ppvObject = static_cast<IFolderView*>(this); return S_OK; } *ppvObject = nullptr; return E_NOINTERFACE; } // IServiceProvider, defer services calls to this QI HRESULT QueryService(REFGUID guidService, REFIID riid, void** ppvObject) { return QueryInterface(riid, ppvObject); } // IOleWindow HRESULT GetWindow(HWND* phwnd) { *phwnd = _hwnd; return S_OK; } HRESULT ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; } // IFolderView HRESULT GetCurrentViewMode(UINT* pViewMode) { return E_NOTIMPL; } HRESULT SetCurrentViewMode(UINT ViewMode) { return E_NOTIMPL; } HRESULT GetFolder(REFIID riid, void** ppv) { return E_NOTIMPL; } HRESULT Item(int iItemIndex, PITEMID_CHILD* ppidl) { return E_NOTIMPL; } HRESULT ItemCount(UINT uFlags, int* pcItems) { return E_NOTIMPL; } HRESULT Items(UINT uFlags, REFIID riid, void** ppv) { if (riid == IID_IShellItemArray) return SHCreateShellItemArrayFromShellItem(_item, riid, ppv); *ppv = nullptr; return E_NOTIMPL; } HRESULT GetSelectionMarkedItem(int* piItem) { return E_NOTIMPL; } HRESULT GetFocusedItem(int* piItem) { return E_NOTIMPL; } HRESULT GetItemPosition(PCUITEMID_CHILD pidl, POINT* ppt) { return E_NOTIMPL; } HRESULT GetSpacing(POINT* ppt) { return E_NOTIMPL; } HRESULT GetDefaultSpacing(POINT* ppt) { return E_NOTIMPL; } HRESULT GetAutoArrange() { return E_NOTIMPL; } HRESULT SelectItem(int iItem, DWORD dwFlags) { return E_NOTIMPL; } HRESULT SelectAndPositionItems(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, POINT* apt, DWORD dwFlags) { return E_NOTIMPL; } }; LPCWSTR path = L"C:\\Users\\Username\\Desktop\\folder"; IContextMenu2* _cm2 = nullptr; IContextMenu3* _cm3 = nullptr; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (_cm3) { LRESULT lr = 0; if (SUCCEEDED(_cm3->HandleMenuMsg2(message, wParam, lParam, &lr))) return lr; } else if (_cm2) { if (SUCCEEDED(_cm2->HandleMenuMsg(message, wParam, lParam))) return 0; } const int buttonId = 1; const int buttonX = 10; const int buttonY = 10; switch (message) { case WM_CREATE: CreateWindow(L"BUTTON", L"Show Context menu", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, buttonX, buttonY, 200, 30, hwnd, (HMENU)(long)buttonId, nullptr, nullptr); break; case WM_COMMAND: if (LOWORD(wParam) == buttonId) { IShellItem* item; IBindCtx* ctx; CreateBindCtx(0, &ctx); // use a binding context to avoid the shell to be too smart and get back wrong items SHCreateItemFromParsingName(path, ctx, IID_PPV_ARGS(&item)); //ctx->Release(); if (item) { IContextMenu* cm; item->BindToHandler(ctx, BHID_SFViewObject, IID_PPV_ARGS(&cm)); if (cm) { if (FAILED(cm->QueryInterface(&_cm3))) { cm->QueryInterface(&_cm2); } // pass site to enable some menu items properly like "share" Site site{ hwnd, item }; IObjectWithSite* ows; cm->QueryInterface(&ows); if (ows) { ows->SetSite(static_cast<IServiceProvider*>(&site)); } auto menu = CreatePopupMenu(); const int firstId = 1; cm->QueryContextMenu(menu, 0, firstId, 0x7FFF, CMF_NORMAL); // remove some flags if not needed POINT pt{ buttonX, buttonY }; ClientToScreen(hwnd, &pt); auto cmd = TrackPopupMenu(menu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, nullptr); if (cmd) { CMINVOKECOMMANDINFO cmi{}; cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(cmd - firstId); cmi.nShow = SW_SHOWNORMAL; cm->InvokeCommand(&cmi); } if (ows) { ows->SetSite(nullptr); } cm->Release(); DestroyMenu(menu); if (_cm2) { _cm2->Release(); _cm2 = nullptr; } if (_cm3) { _cm3->Release(); _cm3 = nullptr; } } item->Release(); } } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { auto hmod = LoadLibrary(L"shell32.dll"); auto fileIconInit = (BOOL(WINAPI*)(BOOL))GetProcAddress(hmod, MAKEINTRESOURCEA(660)); if (fileIconInit) { fileIconInit(TRUE); } WNDCLASS wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"MyWindowClass"; RegisterClass(&wc); auto hwnd = CreateWindow(wc.lpszClassName, L"Context Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
内容的提问来源于stack exchange,提问作者Joe J
相关产品推荐
相关产品推荐

