您确实需要一个ShellExtension
您所需的并不像您想象的那么简单。多个文件选择的正常行为是在新窗口/应用程序实例中打开每个文件。实际上,它只是将所选文件发送到已注册的应用程序,并由应用程序决定如何处理它们。
但至少有1种快速简便的替代方法:
方法1:使用“发送到”
打开“发送到”文件夹("C:\Users\YOURNAME\AppData\Roaming\Microsoft\Windows\SendTo"
),并添加一个应用程序条目。目标应该是您希望将文件选择提供/发送给的应用程序:
"C:\Program Files\That Other App\OtherApp.exe "
你不需要使用"%1"占位符或其他任何东西。你不必编写中介来处理任何事情,只需直接将文件发送到实际应用程序即可。只要应用程序在命令行上接受多个文件,它就可以正常工作。
唯一的小问题是它位于“共享”或常规子菜单上,而不是顶级上下文菜单。它也不是“智能”的,因为它适用于任何文件扩展名,而不像一个正确的ContextMenu处理程序,但它是一个快速简单的、无代码的解决方案,已经存在很长时间了。
方法二:更改动词修饰符
您还可以更改动词修饰符/模式,这听起来是最简单的方法。例如,VideoLan的VLC播放器:
如果您点击多个.MP4文件而不是打开多个实例,它将使用其中一个打开并将其余文件排队播放。这是通过修改注册表中的动词完成的:
+ VLC.MP4
+ shell
+ Open
- MultiSelectModel = Player
+ Command
- (Default) "C:\Program Files.... %1"
MultiSelectModel
是Open
动词的修饰符:
- Single适用于仅支持单个项目的动词
- Player适用于支持任意数量项目的动词
- Document适用于为每个项目创建顶级窗口的动词
对于我的MediaProps小程序,由于它涉及相同的文件类型,我通过添加一个设置为MultiSelectModel.Player
的ViewProps
动词来将我的动词附加到VLC的文件类型上,并且通常运行良好,以至于我的动词不会使VLC出现混乱。
不幸的是,仍然有一些问题我还没有找到。似乎Windows仍然没有像预期的那样将所有文件粘合在一起-即使我自己创建了动词。注册表配置或应用中可能缺少一步-但是由于还有另外两种完成相同任务的方法,所以我从未进一步调查过。
方法三:创建ShellExtension / 上下文菜单处理程序
许多提出的解决方案最终都变成了一个打地鼠游戏,您必须修复介入应用程序中的相同1文件-1实例问题,以便它可以将连接的参数传递给最终的操作者。由于最终结果是拥有一个资源管理器上下文菜单来执行一些有用的操作,因此我们只需为这个其他应用程序构建一个ShellExtension。
这很容易,因为CodeProject上已经完成并可用一个框架:如何使用.NET语言编写Windows Shell扩展。这是一个带有完整ShellExtension项目的MS-PL文章。
通过进行一些修改,这将完美地工作:
- 为多个文件类型设置关联
- 收集多个点击的文件
- 将它们格式化成命令行参数集
- 将命令行传递给实际的工作应用程序
- 提供自定义内容菜单
- 显示时尚的菜单图标
这个测试平台是一个小程序,可以显示媒体文件的MediaInfo属性(如持续时间、帧大小、编解码器、格式等)。除了接受拖放文件外,它还使用ContextMenu DLL助手来接受在资源管理器中选择的多个文件,并将它们传递给单实例显示应用程序。
非常重要的提示
自首次发布以来,我已经修订和更新了原始的MS-PL文章,使其更易于使用。修订版也在CodeProject上.NET中的资源管理器Shell扩展(修订版),仍包含VB和C#版本。
在修订版中,不必到处更改,而是将它们合并为单个变量块。该文章还解释了为什么您可能想要使用C#版本,并提供了解释为什么使用托管代码作为Shell扩展的不好的想法的文章链接。
'模型'仍然是一个Shell扩展,只需启动相关应用程序。
本答案的余下部分仍值得阅读,以了解一般概念和背景。即使很多代码更改部分不适用于修订版,但事实上修改它似乎不太合适。
1. 更新程序集/项目数值
例如,我将程序集名称更改为“MediaPropsShell”。我还删除了根命名空间,但这是可选的。
添加您选择的PNG图标。
选择适当的平台。 由于原始版本有2个安装程序,您可能必须专门为32位操作系统构建x86版本。AnyCPU对64位操作系统有效,但我不确定对于x86是否也适用。大多数使用此模型的系统都会提供一个32位和64位的DLL作为shell扩展助手,但过去大多数不能基于.NET,其中AnyCPU是一个选项。
保持目标平台为NET 4。 如果您没有阅读CodeProject文章或之前没有进行研究,则这一点很重要。
2. 代码更改
如在CodeProject上发布的那样,处理程序仅传递一个文件并仅与一个文件类型关联。下面的代码实现了多个文件类型的处理程序。您还需要修复菜单名称等。所有更改都记录在以下代码中,前缀为{PL}
:
<ClassInterface(ClassInterfaceType.None),
Guid("1E25BCD5-F299-496A-911D-51FB901F7F40"), ComVisible(True)>
Public Class MediaPropsContextMenuExt
Implements IShellExtInit, IContextMenu
Private selectedFiles As List(Of String)
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()
selectedFiles = New List(Of String)
Dim bmp As Bitmap = My.Resources.View
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
Private Sub OnVerbDisplayFileName(ByVal hWnd As IntPtr)
For n As Integer = 0 To selectedFiles.Count - 1
args &= String.Format(" {0}{1}{0} ", quote, selectedFiles(n))
Next
MessageBox.Show("Cmd to execute: " & Environment.NewLine & "[" & cmd & "]", "ShellExtContextMenuHandler")
End Sub
#Region "Shell Extension Registration"
Private Shared exts As String() = {".avi", ".wmv", ".mp4", ".mpg", ".mp3"}
<ComRegisterFunction()>
Public Shared Sub Register(ByVal t As Type)
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
End Try
Next
End Sub
<ComUnregisterFunction()>
Public Shared Sub Unregister(ByVal t As Type)
For Each s As String In exts
Try
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, s)
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw
End Try
Next
End Sub
#End Region
在 IShellExtInit 成员
区域下面,还需要更改一点内容:
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
Dim dataObject As System.Runtime.InteropServices.ComTypes.IDataObject = Marshal.GetObjectForIUnknown(pDataObj)
dataObject.GetData(fe, stm)
Try
Dim hDrop As IntPtr = stm.unionmember
If (hDrop = IntPtr.Zero) Then
Throw New ArgumentException
End If
Dim nFiles As UInteger = NativeMethods.DragQueryFile(hDrop,
UInt32.MaxValue, Nothing, 0)
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
Finally
NativeMethods.ReleaseStgMedium((stm))
End Try
End Sub
原始代码实际上有一个多文件方法的代码,但是被注释了。在添加一个之前我并没有注意到它。更改的部分在星号字符串之间。
另外,很遗憾地说,使用Option Strict,你将不得不对Microsoft的代码进行10个左右的小修改。只需接受IntelliSense建议的更改即可。
重要说明
提供ContextMenu服务的独立DLL模型代表一个EXE“引擎”的常见方法。这就是您经常在程序可执行文件所在文件夹中看到的所有xxxShell.DLL
文件。不同之处在于,您正在构建DLL,而不是相关应用程序的作者。
- 除一个之外的所有更改都在
FileContextMenuExt
类中进行。
- 请务必更改GUID,否则您的处理程序可能会与基于相同MS模板的其他处理程序冲突!您的
工具
菜单上有一个方便的实用程序可供使用。
- BMP/PNG是可选的。
- 最初的MS版本只显示所选文件的名称。因此,相关过程的名称为
OnVerbDisplayFileName
。如您所见,我没有更改它。如果您将其更改以匹配实际操作,则还需要更改IContextMenu
的PInvoke代码中对其的一些引用。不过,除了您之外,没有人会看到该名称。
- 调试MessageBox是调用操作中存在的所有内容。您可以查看我的实际代码。
原始的MS项目中的ReadMe文件描述了这一点,但是在编译后,将该文件复制到它所在的位置并注册:
regasm <asmfilename>.DLL /codebase
取消注册:
regasm <asmfilename>.DLL /unregister
使用位于
Microsoft.NET\Framework64\v4.0.xxxx
文件夹中的
RegAsm
。这将需要在管理员权限的命令窗口(或等效脚本)中完成。另外,对于已部署的应用程序,您可以使用
Public Regster/UnRegister
方法让目标应用程序注册/注销辅助 DLL。
警告:在编译之前,
务必仔细修改您的代码,并测试循环和字符串格式等内容;您希望尽可能少地进行编译测试迭代。原因是一旦激活新的上下文菜单,DLL 就被资源管理器使用,不能被新构建替换。您必须终止
explorer.exe
进程(不仅仅是文件资源管理器!)才能注册并尝试新的构建。
也许还有另一种方法,但我只是关闭任何资源管理器窗口,然后注销并立即重新登录。
测试
如果我右键单击其中一种已注册的文件类型,我会得到预期的菜单,具有正确的菜单文本和位图图像:
点击查看更大的图片
如果我点击,小程序会按预期带有多个文件的一个实例弹出:
点击查看大图
请注意底部的“上一张/下一张”按钮已启用,可以在多个文件之间移动,而当仅加载1个文件时,情况并非如此。
在我的机器上有效TM
资源
如何使用.NET语言编写Windows Shell扩展。这是一个带有完成的ShellExtension项目的MS-PL文章。上述是一组修改,使其能够处理多个扩展和多个文件,因此原始项目是必需的起点。
快捷菜单处理程序和多个动作的最佳实践
选择静态或动态快捷菜单方法
动作和文件关联