You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:32:05