0

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

  1. Parse commands from the registry
  2. Track the verb of the selected command
  3. 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 the command key

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;
}
9
  • Does IFolderView::Items get called? ItemCount? Commented Jan 13 at 14:57
  • @Anders IFolderView::Items is called only when WM_INITMENUPOPUP message is sent for the item Give access to. This item is not in HKEY_CLASSES_ROOT\Directory\Background\shell. ItemCount is never called. Commented Jan 13 at 16:17
  • If you replace SHCreateItemFromParsingName with the classic way you could perhaps test on Windows XP and see if the same problem exists there? Commented Jan 13 at 16:23
  • @Anders Unfortunately I don't have Windows XP. I need a solution for Windows 10/11 Commented Jan 13 at 17:51
  • Not sure exactly what you don't see if you use CMF_NORMAL | CMF_EXPLORE | CMF_EXTENDEDVERBS for example, not only CMF_NORMAL ? Commented Jan 13 at 18:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.