I'm trying to get the menu that appears when right-clicking on an empty area inside a folder (background menu) using IContextMenu::QueryContextMenu. I can get the menu, all the items work fine except for those items that are registered in the registry here HKEY_CLASSES_ROOT\Directory\Background\shell. When I try to select a menu item coming from this section, nothing happens (InvokeCommand fails with code -2147023741 and Error lookup tool returns "No application is associated with the specified file for this operation. " for this code).
One of the possible solutions I found is this
- Parse commands from the registry
- Track the verb of the selected command
- If the verb of the selected command matches one of the commands from here
HKEY_CLASSES_ROOT\Directory\Background\shell\verb\command, then execute this command manually using the command that is recorded in thecommandkey
However, I want to make the commands from the HKEY_CLASSES_ROOT\Directory\Background\shell section work without using such hacks. How can I do this?
To run my code, replace the value of the path variable with the path to your folder and press Context Menu button.
#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;
}
IFolderView::Itemsis called only whenWM_INITMENUPOPUPmessage is sent for the itemGive access to. This item is not inHKEY_CLASSES_ROOT\Directory\Background\shell. ItemCount is never called.CMF_NORMAL | CMF_EXPLORE | CMF_EXTENDEDVERBSfor example, not onlyCMF_NORMAL?