1

I need to display the Windows native context menu of Open with in my application and I already can show it. However, I encountered a problem is I can't execute any Apps (Photos/Paint/...) in Open with submenu properly.

For example, I press right click on a jpg image and hover cursor to open with, then choose Paint to open it, but nothing happen (no exeception, error) after clicking Paint (There is no Paint process in Task Manager).

The screenshot below can reveals my problem precisely, Apps in red block can't be execute properly (Neither native nor third-party applications can be executed). But Search the Microsoft Store and Choose another app can work well.

enter image description here

I found that @yberk 's post also mentioned this problem, but he didn't find any solution

I have read lots of documents and examples, but still can't figure out the problem.

By the way, my development environment is .NET Framework 4.7.2 on Windows10 2004 version.

The following is my code snippet

// My entry point. Right click on the tofu.png
static void Main(string[] args)
{
        
    FileInfo[] files = new FileInfo[1];
    files[0] = new FileInfo(@"k:\qqq\tofu.png");
    ShellContextMenu scm = new ShellContextMenu();
    scm.ShowContextMenu(files, Cursor.Position);
}

Overwrite WindowMessages - Handle user's behavior on the context menu

protected override void WndProc(ref Message m)
{
    #region IContextMenu

    if (_oContextMenu != null &&
        m.Msg == (int)WM.MENUSELECT &&
        ((int)ShellHelper.HiWord(m.WParam) & (int)MFT.SEPARATOR) == 0 &&
        ((int)ShellHelper.HiWord(m.WParam) & (int)MFT.POPUP) == 0)
    {
        string info = string.Empty;

        if (ShellHelper.LoWord(m.WParam) == (int)CMD_CUSTOM.ExpandCollapse)
            info = "Expands or collapses the current selected item";
        else
        {
            info = ""
        }
    }
    #endregion

    #region IContextMenu2
    if (_oContextMenu2 != null &&
        (m.Msg == (int)WM.INITMENUPOPUP ||
            m.Msg == (int)WM.MEASUREITEM ||
            m.Msg == (int)WM.DRAWITEM))
    {
        if (_oContextMenu2.HandleMenuMsg((uint)m.Msg, m.WParam, m.LParam) == S_OK)
            return;
    }
    #endregion

    #region IContextMenu3
    if (_oContextMenu3 != null &&
        m.Msg == (int)WM.MENUCHAR)
    {
        if (_oContextMenu3.HandleMenuMsg2((uint)m.Msg, m.WParam, m.LParam, IntPtr.Zero) == S_OK)
            return;
    }
    #endregion

    base.WndProc(ref m);
}

Show the context menu while right clicking on a file

private void ShowContextMenu(Point pointScreen)
{
    IntPtr pMenu = IntPtr.Zero,
        iContextMenuPtr = IntPtr.Zero,
        iContextMenuPtr2 = IntPtr.Zero,
        iContextMenuPtr3 = IntPtr.Zero;

    try
    {
        // Gets the interfaces to the context menu (IContextMenu)
        if (false == GetContextMenuInterfaces(_oParentFolder, _arrPIDLs, out iContextMenuPtr))
        {
            ReleaseAll();
            return;
        }
        // Create main context menu instance            
        pMenu = CreatePopupMenu();

        // Get all items of context menu
        int nResult = _oContextMenu.QueryContextMenu(
            pMenu,
            0,
            CMD_FIRST,
            CMD_LAST,
            CMF.EXPLORE |
            CMF.NORMAL |
            ((Control.ModifierKeys & Keys.Shift) != 0 ? CMF.EXTENDEDVERBS : 0));

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


        _oContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr2, typeof(IContextMenu2));
        _oContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr3, typeof(IContextMenu3));

        // wait for the user to select an item and will return the id of the selected item.
        uint nSelected = TrackPopupMenuEx(
            pMenu,
            TPM.RETURNCMD,
            pointScreen.X,
            pointScreen.Y,
            this.Handle,
            IntPtr.Zero);

        if (nSelected != 0)
        {
            InvokeCommand(_oContextMenu, nSelected, _strParentFolder, pointScreen);
        }
    }
    catch
    {
        throw;
    }
    finally
    {
        ReleaseAll();
    }
}

InvokeCommand - trigger specific command based on lpverb (get that by id position)

private void InvokeCommand(IContextMenu oContextMenu, uint nCmd, string strFolder, Point pointInvoke)
{
    CMINVOKECOMMANDINFOEX invoke = new CMINVOKECOMMANDINFOEX();
    invoke.cbSize = cbInvokeCommand;
    invoke.lpVerb = (IntPtr)(nCmd - CMD_FIRST);
    invoke.lpDirectory = strFolder;
    invoke.lpVerbW = (IntPtr)(nCmd - CMD_FIRST);
    invoke.lpDirectoryW = strFolder;
    invoke.fMask = CMIC.UNICODE | CMIC.PTINVOKE |
        ((Control.ModifierKeys & Keys.Control) != 0 ? CMIC.CONTROL_DOWN : 0) |
        ((Control.ModifierKeys & Keys.Shift) != 0 ? CMIC.SHIFT_DOWN : 0);
    invoke.ptInvoke = new POINT(pointInvoke.X, pointInvoke.Y);
    invoke.nShow = SW.SHOWNORMAL;

    oContextMenu.InvokeCommand(ref invoke);
}
  • Do you have a small compile-ready reproducing project? – Simon Mourier Dec 04 '20 at 08:10
  • @SimonMourier You can get my compressed VS project from this [google drive link](https://drive.google.com/file/d/1cxXlFJOF62IUDb3LhI6g4AKE3-79ri9j/view?usp=sharing). Thanks for your help. – Pei-Yao Chang Dec 04 '20 at 13:38
  • With your sample, "open with" sub menu items works for me, with an existing file, for example a .png with "Open With and "Paint". – Simon Mourier Dec 04 '20 at 17:43
  • @SimonMourier Would you mind to share your environment? Windows version, visual studio version and how did you compile the code? Since I am a newbie on C# and unfamiliar to visual studio, maybe I missed some important steps... By the way, items in your context menu are same as the context menu when you right clicking on png file in Explore? Because I can only get the basic items and miss lots of items of third-party applications. – Pei-Yao Chang Dec 04 '20 at 23:59
  • Just took your project, set a file path and run. Windows 10 20H2 x64 Visual Studio 2019. I tried debug release compile x64 x86 doesn't change anything (it shouldn't). No the items menus are not exactly the same but open with works fine. Another way to do it is use SHCreateDefaultContextMenu, pass an IShellFolder custom implementation (use ICustomQueryInferface to determine what's needed) with absolute pidls: https://stackoverflow.com/questions/61613374/how-to-use-shcreatedefaultcontextmenu-to-display-the-shell-context-menu-for-mu – Simon Mourier Dec 05 '20 at 08:51
  • @SimonMourier I've tried on three different PCs (VM, notebook, desktop), none of them can work. Do you have any idea about the reason why it could work well on your PC? – Pei-Yao Chang Dec 07 '20 at 03:16
  • Not really. What I do know is the open with is related to HandleMenuMsg(2) support. Note you can dump all IContextMenu2 references (shell supports IContextMenu3 for a long time) and simply forward all windows messages in WndProc to HandleMenuMsg2 without any more fuss, like this: `void WndProc(ref Message m){if(HandleContextMenuMessage(m))return;base.WndProc(ref m);}` – Simon Mourier Dec 07 '20 at 07:47

1 Answers1

1

Finally, I found the reason. We need put the [STAThread] on the entry point.

See windows document STAThread

This attribute must be present on the entry point of any application that uses Windows Forms; if it is omitted, the Windows components might not work correctly. If the attribute is not present, the application uses the multithreaded apartment model, which is not supported for Windows Forms.

namespace test
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            FileInfo[] files = new FileInfo[1];            
            files[0] = new FileInfo(@"K:\qqq\tofu.png");
            ShellContextMenu scm = new ShellContextMenu();
            scm.ShowContextMenu(files, Cursor.Position);
        }
    }
    .
    .
    .
}