Search code examples
c#.netwinapishell-extensions

Different Shell Context Menu versus windows explorer context menu


Hi I have implementation of the IShellFolder com interface in my .NET program when I have get all items from shell context menu. But I have a problem where my tracking shell context menu have some different items versus explorer shell context menu. In the pictures below you have seen that in my program I do not view Open in program sub menu. I have only one item "Open in program". And in my context menu missing Open in Notepad++ and I have in addition some other items like 7-zip item and submenu and CRC SHA from 7-zip program too.

The first picture is shell context menu from windows explorer and the second picture is from my shell context menu code.

Can you tell me where I have an error? Thank you very much.

Shell Context Menu from windows explorer

Shell Context Menu from my code

And this is my code:

private ContextMenu CreateFileContextMenu(FileInfo[] files, Point location)
{
    Win32APICaller.CoInitializeEx(IntPtr.Zero, COINIT.MULTITHREADED);
    IShellFolder parentFolder = GetParentFolder(files[0].DirectoryName);
    IntPtr[] pidls = this.GetPIDLs(parentFolder, files);

    IntPtr pMenu = IntPtr.Zero;
    IntPtr iContextMenuPtr = IntPtr.Zero;
    IntPtr iContextMenuPtr2 = IntPtr.Zero;
    IntPtr iContextMenuPtr3 = IntPtr.Zero;

    if (pidls != null)
    {
        IContextMenu contextMenu;
        if (this.GetContextMenuInterfaces(parentFolder, pidls, out contextMenu, out iContextMenuPtr))
        {
            pMenu = Win32APICaller.CreatePopupMenu();

            Marshal.QueryInterface(iContextMenuPtr, ref IID_IContextMenu2, out iContextMenuPtr2);
            Marshal.QueryInterface(iContextMenuPtr, ref IID_IContextMenu3, out iContextMenuPtr3);

            contextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr2, typeof(IContextMenu2));
            contextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr3, typeof(IContextMenu3));

            int nResult = contextMenu.QueryContextMenu(pMenu, 0, 1, 30000, CMF.EXPLORE | CMF.CANRENAME | CMF.NORMAL | CMF.INCLUDESTATIC | CMF.EXTENDEDVERBS);
            int count = Win32APICaller.GetMenuItemCount(pMenu);

            //contextMenu3.QueryContextMenu(pMenu, 0, 1, 30000, /*CMF.EXPLORE | CMF.NORMAL |*/ CMF.EXTENDEDVERBS);

            count = Win32APICaller.GetMenuItemCount(pMenu);
            Win32APICaller.SendMessage(this.Handle, WM_INITMENUPOPUP, pMenu, 0);
            uint nSelected = Win32APICaller.TrackPopupMenuEx(pMenu, 0x0100, location.X, location.Y, this.Handle, IntPtr.Zero);
       }
   }
}

private IntPtr[] GetPIDLs(IShellFolder parentFolder, FileInfo[] files)
{
    if (parentFolder != null)
    {
        IntPtr[] pidls = new IntPtr[files.Length];
        for (int index = 0; index < files.Length; index++)
        {
            FileInfo fileInfo = files[index];

            uint pchEaten = 0;
            SFGAO pdwAttributes = 0;
            IntPtr pPIDL = IntPtr.Zero;
            int nResult = parentFolder.ParseDisplayName(this.Handle, IntPtr.Zero, fileInfo.Name, ref pchEaten, out pPIDL, ref pdwAttributes);
            if (nResult == 0)
            {
                pidls[index] = pPIDL;
            }
        }

        return pidls;
    }

    return null;
}

private IShellFolder GetParentFolder(string folderName)
{
    IShellFolder desktopFolder = this.GetDektopFolder();
    if (desktopFolder != null)
    {
        IntPtr pPIDL = IntPtr.Zero;
        uint pchEaten = 0;
        SFGAO pdwAttributes = 0;
        int nResult = desktopFolder.ParseDisplayName(this.Handle, IntPtr.Zero, folderName, ref pchEaten, out pPIDL, ref pdwAttributes);
        if (nResult == 0)
        {
            IntPtr pStrRet = Marshal.AllocCoTaskMem(260 * 2 + 4);
            Marshal.WriteInt32(pStrRet, 0, 0);
            nResult = desktopFolder.GetDisplayNameOf(pPIDL, SHGNO.FORPARSING, pStrRet);
            StringBuilder strFolder = new StringBuilder(260);
            Win32APICaller.StrRetToBuf(pStrRet, pPIDL, strFolder, 260);
            Marshal.FreeCoTaskMem(pStrRet);
            pStrRet = IntPtr.Zero;

            IntPtr pUnknownParentFolder = IntPtr.Zero;
            nResult = desktopFolder.BindToObject(pPIDL, IntPtr.Zero, ref IID_IShellFolder, out pUnknownParentFolder);
            Marshal.FreeCoTaskMem(pPIDL);
            if (nResult == 0)
            {
                return (IShellFolder)Marshal.GetTypedObjectForIUnknown(pUnknownParentFolder, typeof(IShellFolder));
            }
        }
    }

    return null;
}

private IShellFolder GetDektopFolder()
{
    IntPtr pUnknownDesktopFolder = IntPtr.Zero;

    int nResult = Win32APICaller.SHGetDesktopFolder(out pUnknownDesktopFolder);
    if (nResult == 0)
        return (IShellFolder)Marshal.GetTypedObjectForIUnknown(pUnknownDesktopFolder, typeof(IShellFolder));
    else
        return null;
}

private bool GetContextMenuInterfaces(IShellFolder parentFolder, IntPtr[] pidls, out IContextMenu contextMenu, out IntPtr contextMenuPtr)
{
    int nResult = parentFolder.GetUIObjectOf(this.Handle, (uint)pidls.Length, pidls, IID_IContextMenu, IntPtr.Zero, out contextMenuPtr);
    contextMenu = null;

    if (nResult == 0)
    {
        contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(contextMenuPtr, typeof(IContextMenu));
        return true;
    }

    return false;
}

protected override void WndProc(ref Message m)
{
    if (this.contextMenu3 != null)
    {
        this.contextMenu3.HandleMenuMsg2((uint)m.Msg, m.WParam, m.LParam, m.Result);
    }
    else if (this.contextMenu2 != null)
    {
        this.contextMenu2.HandleMenuMsg((uint)m.Msg, m.WParam, m.LParam);
    }

    base.WndProc(ref m);
}

Solution

  • In the pictures below you have seen that in my program I do not view Open in program sub menu. I have only one item "Open in program".

    enter image description here

    The reason for this is that these submenus are delay-generated (which explains why they don’t contain anything interesting when you expand them) and owner-drawn.

    So you need to handle messages associated with owner-drawn menu items.

    After that you will get what you expected like this:

    enter image description here

    The following is an Win32 C++ sample code you can refer to:

    #define SCRATCH_QCM_FIRST 1
    #define SCRATCH_QCM_LAST  0x7FFF
    
    IContextMenu2* g_pcm2;
    IContextMenu3* g_pcm3;
    
    //...
    
    void OnContextMenu(HWND hwnd, int xPos, int yPos)
    {
        WCHAR pszFilePath[] = L"C:\\Users\\me\\Desktop\\test1.txt";
        IShellFolder* psfDesktop = NULL;
        ITEMIDLIST* id = 0;
        LPCITEMIDLIST idChild = 0;
        IContextMenu* pcm = NULL;
        int iCmdTemp = 0;
    
        POINT pt = { xPos, yPos };
        if (pt.x == -1 && pt.y == -1) {
            pt.x = pt.y = 0;
            ClientToScreen(hwnd, &pt);
        }
    
        SHParseDisplayName(pszFilePath, 0, &id, 0, 0);
        SHBindToParent(id, IID_IShellFolder, (void**)& psfDesktop, &idChild);
    
        psfDesktop->GetUIObjectOf(hwnd, 1, (const ITEMIDLIST **)&idChild, __uuidof(IContextMenu), NULL, (void **)&pcm);
    
        if (pcm) {
            HMENU hmenu = CreatePopupMenu();
            if (hmenu) {
                if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                    SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                    CMF_NORMAL))) {
    
                    pcm->QueryInterface(IID_IContextMenu2, (void**)& g_pcm2);
                    pcm->QueryInterface(IID_IContextMenu3, (void**)& g_pcm3);
    
                    int iCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD,
                        pt.x, pt.y, hwnd, NULL);
                    if (g_pcm2) {
                        g_pcm2->Release();
                        g_pcm2 = NULL;
                    }
                    if (g_pcm3) {
                        g_pcm3->Release();
                        g_pcm3 = NULL;
                    }
                    if (iCmd > 0) {
                        CMINVOKECOMMANDINFOEX info = { 0 };
                        info.cbSize = sizeof(info);
                        info.fMask = 0x00004000;
                        info.hwnd = hwnd;
                        iCmdTemp = iCmd - SCRATCH_QCM_FIRST;
                        info.lpVerb = MAKEINTRESOURCEA(iCmdTemp);
                        info.lpVerbW = MAKEINTRESOURCEW(iCmdTemp);
                        info.nShow = SW_SHOWNORMAL;
                        pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)& info);
                    }
    
                }
                DestroyMenu(hmenu);
            }
            pcm->Release();
        }
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        int xPos;
        int yPos;
    
        if (g_pcm3) {
            LRESULT lres;
            if (SUCCEEDED(g_pcm3->HandleMenuMsg2(message, wParam, lParam, &lres))) {
                return lres;
            }
        }
        else if (g_pcm2) {
            if (SUCCEEDED(g_pcm2->HandleMenuMsg(message, wParam, lParam))) {
                return 0;
            }
        }
    
    
        switch (message)
        {
        case WM_CONTEXTMENU:
            xPos = GET_X_LPARAM(lParam);
            yPos = GET_Y_LPARAM(lParam);
            OnContextMenu(hWnd, xPos, yPos);
            break;
    //...