How to use 'SHCreateDefaultContextMenu' to display the shell context menu for multiple files from different folders

 ̄綄美尐妖づ 提交于 2021-02-05 10:56:14

问题


I want to create a function which displays the shell context menu for a given file list (as string),
but the files are from different location on the shell namespace.
(It is the same the context menu which is shown when you right click selected files in the search results area in Windows Explorer)

I saw that the issue was discussed in couple of questions before, but none of them gave a solution:
1. Showing a Windows context menu for multiple items
2. Display file properties dialog for files in different directories
3. How to display system context menu for multiple files in different folders?

However, I noticed that the comments there are referring to this blog (2007), it is written by the author of a file manager for windows. (his file manager software defenetly shows that he did it)
He mentions overriding IShellFolder::GetUIObjectOf method and implementing a new IDataObject interface.

13 years have been passed since then, so I'm thinking using new functions intorduced in Windows Vista:
1. I'm trying to use SHCreateDefaultContextMenu instead CDefFolderMenu_Create2.
2. Maybe the function SHCreateDataObject can be used instead of implementing a new DataObject?

I'm not a COM expert, and I struggle to do those tasks, Hope someone will fill the missing blocks.

I have put here my effort.
It is implemented as a dll to make it easier for other to use and share.

#include <Windows.h>
#include <shlobj.h>
#define MIN_SHELL_ID 1
#define MAX_SHELL_ID 30000
extern "C"
{
    HRESULT ProcessCMCommand(LPCONTEXTMENU pCM, UINT idCmdOffset, HWND hWnd);


    // Try to convert pICv1 into IContextMenu2 or IcontextMenu3
    // In case of success, release pICv1.
    HRESULT UpgradeContextMenu(
        LPCONTEXTMENU pICv1, // In: The context menu version 1 to be converted.
        void** ppCMout, // Out: The new context menu (or old one in case
                        // the convertion could not be done)
        int* pcmType) // Out: The version number.
    {
        HRESULT hr;
        // Try to get version 3 first.
        hr = pICv1->QueryInterface(IID_IContextMenu3, ppCMout);
        if (NOERROR == hr)
        {
            *pcmType = 3;
        }
        else
        {
            hr = pICv1->QueryInterface(IID_IContextMenu2, ppCMout);
            if (NOERROR == hr)
                *pcmType = 2;
        }

        if (*ppCMout)
        {
            pICv1->Release(); // free version 1
        }
        else { // only version 1 is supported
            *pcmType = 1;
            *ppCMout = pICv1;
            hr = NOERROR;
        }
        return hr;
    }

    // Global ref to the context menu to be used while
    // the window messages are being handled.
    LPCONTEXTMENU2 g_pIContext_v2 = NULL;

    // Handle window messages.
    // The messages: WM_DRAWITEM, WM_MEASUREITEM, WM_INITMENUPOPUP, WM_MENUSELECT
    // should be redirected to this function.
    __declspec(dllexport) void OnWindowMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
    {
        if (g_pIContext_v2 == NULL) // Version 1 is used.
            return;

        switch (msg)
        {
        case WM_DRAWITEM:
        case WM_MEASUREITEM:
            if (wp)
                break;
        case WM_INITMENUPOPUP:
            g_pIContext_v2->HandleMenuMsg(msg, wp, lp);
            break;
        case WM_MENUSELECT:
            // Get the help text and update the status bar , Not implemented yet.
            break;
        }
    }

    // Shows the shell context menu for the given file list at the given position.
    // * error checking is not implemented here.
    __declspec(dllexport) HRESULT ShowContextMenu(
        HWND hWnd,
        int xPos,
        int yPos,
        wchar_t **fullFileNames, // path + file name
        size_t fullFileNamesSize
    )
    {
        HRESULT hr;

        // absolute pidl of the files.
        LPITEMIDLIST *pidlChilds;
        pidlChilds = new LPITEMIDLIST[fullFileNamesSize];

        // Get the desktop folder.
        LPSHELLFOLDER pDesktop;
        SHGetDesktopFolder(&pDesktop);

        size_t pidlChildsSize = 0;
        for (size_t i = 0; i < fullFileNamesSize; i++)
        {
            PIDLIST_ABSOLUTE pidl_absolute;
            UINT attributes;
            hr = SHParseDisplayName(fullFileNames[i], NULL, &pidl_absolute, SFGAO_SYSTEM, (SFGAOF*)&attributes);
            pidlChilds[pidlChildsSize] = pidl_absolute;
            pidlChildsSize++;
        }

        // Probably I need to use the new class in place of the pDesktop arg.  
        DEFCONTEXTMENU dcm =
        {
            hWnd,
            NULL, // contextMenuCB
            NULL, // pidlFolder,
            pDesktop, 
            pidlChildsSize,
            (LPCITEMIDLIST*)pidlChilds,
            NULL, // *punkAssociationInfo
            NULL, // cKeys
            NULL // *aKeys
        };


        IContextMenu *pCMv1 = NULL; // Version 1
        IContextMenu *pCMx = NULL; // Versions 2 or 3

        hr = SHCreateDefaultContextMenu(&dcm, IID_IContextMenu, (void**)&pCMv1);

        int pcmType = 0;

        hr = UpgradeContextMenu(pCMv1, (void**)&pCMx, &pcmType);

        if (pcmType > 1) // pCMv1 was feed inside UpgradeContextMenu function.
        {
            g_pIContext_v2 = (LPCONTEXTMENU2)pCMx;
        }

        HMENU hMenu = CreatePopupMenu();


        hr = pCMx->QueryContextMenu(hMenu, 0, MIN_SHELL_ID, MAX_SHELL_ID, CMF_EXPLORE | CMF_CANRENAME);


        UINT cmdID = TrackPopupMenu(hMenu,
            TPM_LEFTALIGN | TPM_RETURNCMD,
            xPos, yPos, 0, hWnd, 0);


        if (cmdID >= MIN_SHELL_ID && cmdID <= MAX_SHELL_ID)
            ProcessCMCommand(pCMx,
                cmdID - MIN_SHELL_ID,  // offset passed
                hWnd
            );



        // Free resources

        for (size_t i = 0; i < pidlChildsSize; i++)
            CoTaskMemFree(pidlChilds[i]);

        pDesktop->Release();

        pCMx->Release();

        delete[] pidlChilds;

        g_pIContext_v2 = NULL;

        return hr;

    }

    HRESULT ProcessCMCommand(LPCONTEXTMENU pCM, UINT idCmdOffset, HWND hWnd)
    {
        CMINVOKECOMMANDINFOEX ici;
        ZeroMemory(&ici, sizeof(ici));
        ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX);
        ici.hwnd = hWnd;
        ici.fMask = CMIC_MASK_UNICODE;
        ici.lpVerb = MAKEINTRESOURCEA(idCmdOffset);
        ici.lpVerbW = MAKEINTRESOURCEW(idCmdOffset);
        ici.nShow = SW_SHOWNORMAL;

        return pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
    }

    /*
    class CIShellFolderSpy : public IShellFolder
    {
    public:
        // constructor
        CIShellFolderSpy(LPSHELLFOLDER sf) {
            sf->AddRef();
            m_iSF = sf;
        }

        ~CIShellFolderSpy() { m_iSF->Release(); }

        // IUnknown methods --------
        STDMETHOD_(ULONG, AddRef)() {
            return m_iSF->AddRef();
        }

        STDMETHOD_(ULONG, Release)() {
            return m_iSF->Release();
        }

        STDMETHOD_(HRESULT, QueryInterface(REFIID riid, void **ppvObject)) {
            return m_iSF->QueryInterface(riid, ppvObject);
        }

        // IShellFolder methods ----

        STDMETHOD_(HRESULT, BindToObject)(
            PCUIDLIST_RELATIVE pidl,
            IBindCtx *pbc,
            REFIID riid,
            void **ppv) {
            return m_iSF->BindToObject(pidl, pbc, riid, ppv);
        }

        STDMETHOD_(HRESULT, BindToStorage)(
            PCUIDLIST_RELATIVE pidl,
            IBindCtx *pbc,
            REFIID riid,
            void **ppv) {
            return m_iSF->BindToStorage(pidl, pbc, riid, ppv);
        }

        STDMETHOD_(HRESULT, CompareIDs)(
            LPARAM lParam,
            PCUIDLIST_RELATIVE pidl1,
            PCUIDLIST_RELATIVE pidl2) {
            return m_iSF->CompareIDs(lParam, pidl1, pidl2);
        }

        STDMETHOD_(HRESULT, CreateViewObject)(
            HWND hwndOwner,
            REFIID riid,
            void **ppv) {
            return m_iSF->CreateViewObject(hwndOwner, riid, ppv);
        }

        STDMETHOD_(HRESULT, EnumObjects)(
            HWND hwnd,
            SHCONTF grfFlags,
            IEnumIDList **ppenumIDList) {
            return m_iSF->EnumObjects(hwnd, grfFlags, ppenumIDList);
        }

        STDMETHOD_(HRESULT, GetAttributesOf)(
            UINT cidl,
            PCUITEMID_CHILD_ARRAY apidl,
            SFGAOF *rgfInOut) {
            return m_iSF->GetAttributesOf(cidl, apidl, rgfInOut);
        }

        STDMETHOD_(HRESULT, GetDisplayNameOf)(
            PCUITEMID_CHILD pidl,
            SHGDNF uFlags,
            STRRET *pName) {
            return m_iSF->GetDisplayNameOf(pidl, uFlags, pName);
        }

        STDMETHOD_(HRESULT, ParseDisplayName)(
            HWND hwnd,
            IBindCtx *pbc,
            LPWSTR pszDisplayName,
            ULONG *pchEaten,
            PIDLIST_RELATIVE *ppidl,
            ULONG *pdwAttributes
            ) {
            return m_iSF->ParseDisplayName(hwnd,
                pbc,
                pszDisplayName,
                pchEaten,
                ppidl,
                pdwAttributes);
        }

        STDMETHOD_(HRESULT, SetNameOf)(
            HWND hwnd,
            PCUITEMID_CHILD pidl,
            LPCWSTR pszName,
            SHGDNF uFlags,
            PITEMID_CHILD *ppidlOut
            ) {
            return m_iSF->SetNameOf(
                hwnd,
                pidl,
                pszName,
                uFlags,
                ppidlOut);
        }

        virtual HRESULT STDMETHODCALLTYPE GetUIObjectOf(
            HWND hwndOwner,
            UINT cidl,
            LPCITEMIDLIST *apidl,
            REFIID riid,
            UINT *rgfReserved,
            void **ppv)
        {
            if (InlineIsEqualGUID(riid, IID_IDataObject))
            {
                // i ignore the pidl array supplied and return the good data object
                return m_pMDObj->QueryInterface(riid, ppv);
            }
            else {
                // i've seen IDropTarget requests, let base handle them
                return m_iSF->GetUIObjectOf(hwndOwner, cidl, apidl, riid, rgfReserved, ppv);
            }
        }

    protected:
        LPSHELLFOLDER m_iSF;
        CMultiDataObject* m_pMDObj;

    }; // end of class

    */
}

来源:https://stackoverflow.com/questions/61613374/how-to-use-shcreatedefaultcontextmenu-to-display-the-shell-context-menu-for-mu

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!