C# - 如何使用上下文菜单正确触发应用程序打开

Posted

技术标签:

【中文标题】C# - 如何使用上下文菜单正确触发应用程序打开【英文标题】:C# - How to trigger the App in open with context menu properly 【发布时间】:2021-03-16 06:45:53 【问题描述】:

我需要在我的应用程序中显示 Open with 的 Windows 本机上下文菜单,并且我已经可以显示它。但是,我遇到了一个问题是我无法正确执行Open with 子菜单中的任何应用程序(照片/绘画/...)。

例如,我在一张jpg图片上按右键,鼠标悬停打开,然后选择Paint打开,但是点击Paint后没有任何反应(没有异常,错误)(任务管理器中没有Paint进程) .

下面的截图可以准确地揭示我的问题,红色块中的应用程序无法正常执行(无论是原生应用程序还是第三方应用程序都无法执行)。但是Search the Microsoft StoreChoose another app 可以很好地工作。

我发现@yberk 的帖子也提到了这个问题,但他没有找到任何解决方案

Wait for process started by IContextMenu.InvokeCommand

我已经阅读了很多文档和示例,但仍然无法找出问题所在。

A Raymond Chen blog series "How to host an IContextMenu" Explorer Shell Context Menu - Code Project 我从这里获得了几乎所有的示例代码 C# File Browser - Code Project dwmkerr/sharpshell - github Gong Solutions Shell Library

顺便说一下,我的开发环境是.NET Framework 4.7.2 on Windows10 2004版本。

以下是我的代码sn-p

// 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);

覆盖 WindowMessages - 处理用户在上下文菜单上的行为

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);

右键单击文件时显示上下文菜单

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 - 根据 lpverb 触发特定命令(通过 id 位置获取)

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);

【问题讨论】:

你有一个小的可编译的复制项目吗? @SimonMourier 你可以从这个google drive link 获得我的压缩VS 项目。感谢您的帮助。 对于您的示例,“打开方式”子菜单项适用于我,使用现有文件,例如带有“打开方式和“绘制”的 .png。 @SimonMourier 您介意分享您的环境吗? Windows 版本、Visual Studio 版本以及您是如何编译代码的?由于我是 C# 的新手并且对 Visual Studio 不熟悉,也许我错过了一些重要的步骤...顺便说一下,当您在探索中右键单击 png 文件时,您的上下文菜单中的项目与上下文菜单相同?因为我只能拿到基本的物品,错过了很多第三方应用的物品。 刚刚拿到你的项目,设置文件路径并运行。 Windows 10 20H2 x64 Visual Studio 2019。我试过调试版本编译 x64 x86 没有改变任何东西(它不应该)。不,项目菜单不完全相同,但可以正常打开。另一种方法是使用 SHCreateDefaultContextMenu,通过绝对 pidls 传递一个 IShellFolder 自定义实现(使用 ICustomQueryInferface 来确定需要什么):***.com/questions/61613374/… 【参考方案1】:

终于找到了原因。我们需要将[STAThread] 放在入口点上。

见windows文档STAThread

此属性必须存在于任何使用 Windows 窗体的应用程序的入口点上;如果省略,Windows 组件可能无法正常工作。如果该属性不存在,则应用程序使用多线程单元模型,Windows 窗体不支持该模型。

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);
        
    
    .
    .
    .

【讨论】:

以上是关于C# - 如何使用上下文菜单正确触发应用程序打开的主要内容,如果未能解决你的问题,请参考以下文章

在 SharpShell 从上下文菜单启动的程序中继承正确的 app.config

C# Composition - 不完全确定我是不是正确实施

android 长按事件 和 长按弹出上下文菜单如何处理

如何避免 jQuery mmenu 在打开时滚动到顶部

如何在WPF中单击菜单项时在父窗口下打开子窗口?

如何在鼠标光标处显示 NSMenu?