c# - 实现资源管理器ContextMenu并将多个文件传递到一个程序实例

标签 c# .net vb.net windows explorer

Situation



我有一个第三方GUI应用程序,可以通过CLI接受多个文件,
例如:
MyProgram.exe "file1" "file2"

然后将所有文件立即加载到应用程序的同一实例中。

为了优化我的时间,我想通过右键单击 Windows资源管理器中的某些文件来加载多个文件(例如:选择5个文件>右键单击>选择“在MyProgram中打开”命令)

我知道如何创建所需的注册表项,以在上下文菜单中为特定文件类型添加该命令,这不是问题。

Problem



该第三方程序没有任何可从contextmenu捕获多个文件的驱动程序,shell扩展程序或方法,因此,如果不是我从资源管理器中选择2个文件,则每个文件都在该程序的单独实例中打开,没有开发驱动程序的想法,所以驱动程序不是我想要的。

Focus



我愿意接受建议,也许这不是有效的方法,但似乎是最简单的方法:

我的想法是开发一个迷你CLI应用程序以捕获这些多个文件(可能基于Windows消息或SO非 Activity 状态,我不知道这就是我要问的原因),将这些文件/参数写入文本文件,然后加入所有一行中的参数调用我的第三方程序,并使用这些参数在该程序的单个实例中一次加载所有文件。

换句话说,在此第三方应用程序中选择多个文件以一次打开所有文件时,只需一个简单的加载程序即可从上下文菜单中使用它。

Question



首先,我想知道是否存在一个已知术语来命名应用程序的名称,该名称能够在同一实例中加载多个文件,然后从资源管理器中选择文件,然后从contextmenu中选择文件。我想研究那个学期。

在VB.NET/C#控制台应用程序下,哪种方法最有效地完成此任务? (不是司机)

如何开始开发呢?

已知页面(例如 codeproject ...)中是否存在任何源代码示例?

最佳答案

你确实想要一个ShellExtension
您想要的并不像您想的那么简单。选择多个文件的正常行为是在新的Window/App实例中打开每个文件。实际上,它只是将所选文件发送到注册的应用程序,然后将其留给应用程序来决定如何使用它们。
不过,至少有1种快捷简便的选择:
方法1:使用发送到
打开Send To文件夹("C:\Users\YOURNAME\AppData\Roaming\Microsoft\Windows\SendTo")并添加该应用程序的条目。目标将是您希望将文件选择发送/发送到的应用程序:

"C:\Program Files\That Other App\OtherApp.exe "
您不需要“%1”占位符或其他任何内容。您无需编写中介程序即可执行任何操作,只需将文件直接发送到实际应用即可。只要应用程序在命令行上接受多个文件,它就可以正常工作。
唯一的小事是它位于“共享”或常规子菜单上,而不是顶级上下文菜单上。它也不是“智能”的,因为它可用于任何文件扩展名,而不是适当的ContextMenu处理程序,但是它是一种快速,简便,无代码的解决方案,已经存在了很长时间。

方法2:更改动词限定词
您还可以更改动词限定词/模式,这听起来是最简单的方法。以VideoLan的VLC播放器为例:
如果您单击多个.MP4文件而不是打开多个实例,它将打开其中的一个,其余的将排队播放。这可以通过修改注册表中的动词来完成:
+ VLC.MP4
   + shell    
       + Open   
           -  MultiSelectModel = Player
           + Command    
             - (Default) "C:\Program Files.... %1"
             
MultiSelectModelOpen动词的修饰语:
  • 单个用于仅支持单个项目的动词
  • 播放器,用于支持任意数量项的动词
  • 动词文档,可为每个项目创建顶层窗口

  • 对于我的MediaProps小程序,由于它涉及相同的文件类型,因此我通过添加ViewProps动词(将其设置为MultiSelectModel.Player)将动词附加到VLC的文件类型上,只要动词不会混淆VLC,它就可以正常工作。
    不幸的是,我还没有发现一些错误。 Windows似乎仍然无法像预期的那样将所有文件粘合在一起-即使我自己动词。在注册表配置或应用程序中都缺少一个步骤-但是通过其他两种方法可以完成相同的操作,我从没有进行进一步的研究。

    方法3:创建ShellExtension/ContextMenu处理程序
    许多提议的解决方案最终都是Whack-a-Mole游戏,您必须在一个干预应用中修复相同的1文件-1实例问题,以便它可以将串联的参数提供给最终参与者。由于最终结果是要使用 Explorer ContextMenu 来做一些有用的事情,因此只需为此其他应用程序构建一个 ShellExtension
    这很容易,因为已经完成了一个框架,并且该框架在CodeProject:How to Write Windows Shell Extension with .NET Languages上可用。这是MS-PL文章,其中包含完成的ShellExtension项目。
    进行一些修改,这将非常适合:

    多种文件类型的
  • 设置关联
  • 收集单击的多个文件
  • 将它们格式化为命令行arg设置
  • 将命令行传递给实际的工作程序
  • 提供自定义的ContentMenu
  • 显示一个时髦的菜单图标

  • 此测试床是一个小程序,用于显示媒体文件的MediaInfo属性(诸如Duration,Frame Size,Codec,format等之类的内容)。除了接受拖放的文件外,它还使用ContextMenu DLL帮助器来接受在资源管理器中选择的多个文件,并将其提供给“单实例显示”应用程序。

    重要提示
    自从首次发布以来,我对进行了修改和更新,原始MS-PL文章的使其更易于使用。该修订版也位于CodeProject Explorer Shell Extensions in .NET (Revised)上,并且仍包含VB和C#版本。
    在修订版中,不必在各处进行更改,而是将它们合并为单个变量块。本文还解释了为什么您可能要使用C#版本,并提供了指向文章的链接,这些文章解释了为什么对托管扩展程序使用托管代码不是是一个好主意。
    “模型”仍然是仅用于启动相关应用程序的Shell Extension的模型。
    对于一般概念和背景,这个答案的平衡仍然值得一读。即使大部分代码更改部分不适用于该修订版,在事实发生后对其进行良好更改似乎也不对。

    1.更新程序集/项目值
    例如,我将程序集名称更改为“MediaPropsShell”。我还删除了根 namespace ,但这是可选的。
    添加您选择的PNG图标。
    选择适当的平台。 由于原始版本有2个安装程序,因此您可能必须专门为32位操作系统构建x86版本。 AnyCPU可以在64位操作系统上正常工作,我不确定x86。使用此模型的大多数系统都为shell扩展帮助程序提供了32位和64位DLL,但是过去大多数都不能基于NET(在任何CPU中都可以选择AnyCPU)。
    将目标平台保留为NET4。如果您没有阅读CodeProject文章或之前没有进行过研究,则这一点很重要。
    2.代码更改
    正如在CodeProject上发布的那样,处理程序还仅传递一个文件并将其自身仅与一种文件类型相关联。下面的代码实现了多种文件类型的处理程序。您还将需要修复菜单名称等等。所有更改均在下面的序言中用{PL}记录:

    ' {PL} - change the GUID to one you create!
    <ClassInterface(ClassInterfaceType.None),
    Guid("1E25BCD5-F299-496A-911D-51FB901F7F40"), ComVisible(True)>
    
    Public Class MediaPropsContextMenuExt    ' {PL} - change the name
        Implements IShellExtInit, IContextMenu
    
        ' {PL} The nameS of the selected file
        Private selectedFiles As List(Of String)
    
        ' {PL} The names and text used in the menu
        Private menuText As String = "&View MediaProps"
        Private menuBmp As IntPtr = IntPtr.Zero
        Private verb As String = "viewprops"
        Private verbCanonicalName As String = "ViewMediaProps"
        Private verbHelpText As String = "View Media Properties"
    
        Private IDM_DISPLAY As UInteger = 0
        
        Public Sub New()
            ' {PL} - no NREs, please
            selectedFiles = New List(Of String)
    
            ' Load the bitmap for the menu item.
            Dim bmp As Bitmap = My.Resources.View         ' {PL} update menu image
    
            ' {PL} - not needed if you use a PNG with transparency (recommended):
            'bmp.MakeTransparent(bmp.GetPixel(0, 0))
            Me.menuBmp = bmp.GetHbitmap()
        End Sub
    
        Protected Overrides Sub Finalize()
            If (menuBmp <> IntPtr.Zero) Then
                NativeMethods.DeleteObject(menuBmp)
                menuBmp = IntPtr.Zero
            End If
        End Sub
    
        ' {PL} dont change the name (see note)
        Private Sub OnVerbDisplayFileName(ByVal hWnd As IntPtr)
    
            '' {PL} the command line, args and a literal for formatting
            'Dim cmd As String = "C:\Projects .NET\Media Props\MediaProps.exe"
            'Dim args As String = ""
            'Dim quote As String = """"
    
            '' {PL} concat args
            For n As Integer = 0 To selectedFiles.Count - 1
                args &= String.Format(" {0}{1}{0} ", quote, selectedFiles(n))
            Next
    
            ' Debug command line visualizer
            MessageBox.Show("Cmd to execute: " & Environment.NewLine & "[" & cmd & "]", "ShellExtContextMenuHandler")
    
            '' {PL} start the app with the cmd line we made
            'If selectedFiles.Count > 0 Then
            '    Process.Start(cmd, args)
            'End If
    
        End Sub
        
    #Region "Shell Extension Registration"
    
        ' {PL} list of media files to show this menu on (short version)
        Private Shared exts As String() = {".avi", ".wmv", ".mp4", ".mpg", ".mp3"}
    
        <ComRegisterFunction()> 
        Public Shared Sub Register(ByVal t As Type)
            ' {PL}  use a loop to create the associations
            For Each s As String In exts
                Try
                    ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, s,
                        "MediaPropsShell.MediaPropsContextMenuExt Class")
                Catch ex As Exception
                    Console.WriteLine(ex.Message) 
                    Throw ' Re-throw the exception
                End Try
            Next
    
        End Sub
    
        <ComUnregisterFunction()> 
        Public Shared Sub Unregister(ByVal t As Type)
            ' {PL}  use a loop to UNassociate
            For Each s As String In exts
                Try
                    ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, s)
                Catch ex As Exception
                    Console.WriteLine(ex.Message) ' Log the error
                    Throw ' Re-throw the exception
                End Try
            Next
        End Sub
    
    #End Region
    
    也需要在IShellExtInit Members REGION中稍稍更改一下:
    Public Sub Initialize(pidlFolder As IntPtr, pDataObj As IntPtr,
          hKeyProgID As IntPtr) Implements IShellExtInit.Initialize
    
        If (pDataObj = IntPtr.Zero) Then
            Throw New ArgumentException
        End If
    
        Dim fe As New FORMATETC
        With fe
            .cfFormat = CLIPFORMAT.CF_HDROP
            .ptd = IntPtr.Zero
            .dwAspect = DVASPECT.DVASPECT_CONTENT
            .lindex = -1
            .tymed = TYMED.TYMED_HGLOBAL
        End With
    
        Dim stm As New STGMEDIUM
    
        ' The pDataObj pointer contains the objects being acted upon. In this 
        ' example, we get an HDROP handle for enumerating the selected files 
        ' and folders.
        Dim dataObject As System.Runtime.InteropServices.ComTypes.IDataObject = Marshal.GetObjectForIUnknown(pDataObj)
        dataObject.GetData(fe, stm)
    
        Try
            ' Get an HDROP handle.
            Dim hDrop As IntPtr = stm.unionmember
            If (hDrop = IntPtr.Zero) Then
                Throw New ArgumentException
            End If
    
            ' Determine how many files are involved in this operation.
            Dim nFiles As UInteger = NativeMethods.DragQueryFile(hDrop,
                             UInt32.MaxValue, Nothing, 0)
    
            ' ********************
            ' {PL} - change how files are collected
            Dim fileName As New StringBuilder(260)
            If (nFiles > 0) Then
                For n As Long = 0 To nFiles - 1
                    If (0 = NativeMethods.DragQueryFile(hDrop, CUInt(n), fileName,
                             fileName.Capacity)) Then
                        Marshal.ThrowExceptionForHR(WinError.E_FAIL)
                    End If
                    selectedFiles.Add(fileName.ToString)
                Next
            Else
                Marshal.ThrowExceptionForHR(WinError.E_FAIL)
            End If
    
            ' {/PL} 
            ' *** no more changes beyond this point ***
    
            ' [-or-]
            ' Enumerates the selected files and folders.
            '...
           
        Finally
            NativeMethods.ReleaseStgMedium((stm))
        End Try
    End Sub
    
    原始代码实际上确实包含多文件方法的代码,该代码已被注释掉。在添加一个之前,我实际上没有看到它。更改的部分在星弦之间。
    而且,伤心地说,但Option Strict,你将不得不作出10种左右的微小变化,以微软的代码。只需接受IntelliSense建议的更改即可。

    重要说明
    代表EXE“引擎”提供ContextMenu服务的单独DLL的模型是很常见。这就是所有xxxShell.DLL文件的内容,您通常会在文件夹以及程序可执行文件中看到这些文件。此处的区别是您正在构建DLL,而不是所讨论应用程序的作者。
  • 除一个以外的所有更改都在FileContextMenuExt
  • 请确保更改GUID,否则您的处理程序可能会基于同一MS模板与其他程序发生冲突! Tools菜单上有一个方便的实用程序。
  • BMP/PNG是可选的
  • 原始的MS版本仅显示所选文件的名称。因此相关过程称为OnVerbDisplayFileName。如您所见,我没有改变。如果更改它以使其与实际操作相匹配,则还需要在PInvoke重磅代码IContextMenu中更改对其的一些引用。除了你,没人会看到这个名字。
  • 调试MessageBox就是invoke操作的全部内容。您可以看到我的实际代码用法。

  • 原始MS项目中的自述文件对此进行了描述,但是在编译后,将文件复制到将要驻留的位置并进行注册:
    regasm <asmfilename>.DLL /codebase
    
    取消注册:
    regasm <asmfilename>.DLL /unregister
    
    使用在RegAsm文件夹中找到的Microsoft.NET\Framework64\v4.0.xxxx。这必须在具有管理员权限(或等效脚本)的命令窗口中完成。另外,对于已部署的应用程序,可以使用Public Regster/UnRegister方法让目标应用程序注册/注销帮助程序DLL。

    警告:在编译之前,请仔细更改的代码来更改并测试诸如循环和字符串格式之类的内容;您需要尽可能少的编译测试迭代。原因是,一旦激活了新的上下文菜单,资源管理器就将使用该DLL,并且该DLL无法用新的版本替换。您必须终止explorer.exe进程(而不仅仅是File Explorer!)才能注册并尝试新的构建。
    可能还有另一种方法,但是我只关闭了所有Explorer Windows,然后注销并重新打开。

    测试中
    如果我右键单击一种注册的文件类型,则可以使用正确的菜单文本和位图图像按预期获得菜单:

    click for larger image
    如果单击,则在一个实例中,applet会按预期方式出现多个文件:
    enter image description here
    click for larger image
    请注意,如何启用底部的“上一个/下一个”按钮从一个文件移动到另一个文件,而仅加载1个文件则不是这种情况。
    在我的机器上工作

    资源
    How to Write Windows Shell Extension with .NET Languages。这是MS-PL文章,其中包含完成的ShellExtension项目。上面是一组mod,使其可以使用多个扩展名和多个文件,因此需要原始项目作为起点。
    Best Practices for Shortcut Menu Handlers and Multiple Verbs
    Choosing a Static or Dynamic Shortcut Menu Method
    Verbs and File Associations

    关于c# - 实现资源管理器ContextMenu并将多个文件传递到一个程序实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27088510/

    相关文章:

    vb.net - 从Google下载MP3将文本翻译成语音

    c# - 选择用户控件时如何禁用 ASP.NET 页面中的控件?

    c# - 获取作为参数发送的变量的名称

    .net - 创建像 Flash 这样的浏览器插件

    c# - 在临时目录中创建新项目

    arrays - VB删除动态数组中的最后一个元素

    java - 检查是否选择了switch case

    c# - C#从串口读取字节流

    c# - Windows 窗体 Web 浏览器控制缩放级别

    c# - 在这种情况下,为什么 .NET 比 C++ 快?