以编程方式在Windows资源管理器中选择多个文件

24

你能记得你搜索过哪些页面吗?现在这两个链接都已经过时了。 - Wolf
8个回答

18

这应该可以通过使用shell函数SHOpenFolderAndSelectItems实现。

编辑

以下是使用C/C++编写的一些示例代码,没有进行错误检查:

//Directory to open
ITEMIDLIST *dir = ILCreateFromPath(_T("C:\\"));

//Items in directory to select
ITEMIDLIST *item1 = ILCreateFromPath(_T("C:\\Program Files\\"));
ITEMIDLIST *item2 = ILCreateFromPath(_T("C:\\Windows\\"));
const ITEMIDLIST* selection[] = {item1,item2};
UINT count = sizeof(selection) / sizeof(ITEMIDLIST);

//Perform selection
SHOpenFolderAndSelectItems(dir, count, selection, 0);

//Free resources
ILFree(dir);
ILFree(item1);
ILFree(item2);

有关使用此方法的任何其他信息都将非常有帮助...在pinvoke.net上似乎没有任何相关信息,而且我对交互操作不是很擅长。 - devios1
需要注意的是:选择/计数变量看起来不像,但它们在末尾包含一个0分隔符。如果没有它,SHOpenFolderAndSelectItems函数将无法选择所有请求的文件(这意味着在calloc()的情况下,传递项目数量+1)。 - Karsten
如果您不想自己创建程序,可以使用我在 stack overflow 中使用您的答案编写的工具:https://github.com/aurire/windows-explorer-files-selector。查看源代码,随意修改,或者只需使用编译后的 exe 文件和安装说明。 右键单击要选择的文件列表,然后点击“选择文件”,就这么简单。 - Aurimas Rekštys

9
在资源管理器中选择多个文件的正确方法如下:
非托管代码如下(从中国编码帖子编译并修复其错误):
static class NativeMethods
{
    [DllImport("shell32.dll", ExactSpelling = true)]
    public static extern int SHOpenFolderAndSelectItems(
        IntPtr pidlFolder,
        uint cidl,
        [In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
        uint dwFlags);

    [DllImport("shell32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr ILCreateFromPath([MarshalAs(UnmanagedType.LPTStr)] string pszPath);

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214F9-0000-0000-C000-000000000046")]
    public interface IShellLinkW
    {
        [PreserveSig]
        int GetPath(StringBuilder pszFile, int cch, [In, Out] ref WIN32_FIND_DATAW pfd, uint fFlags);

        [PreserveSig]
        int GetIDList([Out] out IntPtr ppidl);

        [PreserveSig]
        int SetIDList([In] ref IntPtr pidl);

        [PreserveSig]
        int GetDescription(StringBuilder pszName, int cch);

        [PreserveSig]
        int SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);

        [PreserveSig]
        int GetWorkingDirectory(StringBuilder pszDir, int cch);

        [PreserveSig]
        int SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);

        [PreserveSig]
        int GetArguments(StringBuilder pszArgs, int cch);

        [PreserveSig]
        int SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);

        [PreserveSig]
        int GetHotkey([Out] out ushort pwHotkey);

        [PreserveSig]
        int SetHotkey(ushort wHotkey);

        [PreserveSig]
        int GetShowCmd([Out] out int piShowCmd);

        [PreserveSig]
        int SetShowCmd(int iShowCmd);

        [PreserveSig]
        int GetIconLocation(StringBuilder pszIconPath, int cch, [Out] out int piIcon);

        [PreserveSig]
        int SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);

        [PreserveSig]
        int SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);

        [PreserveSig]
        int Resolve(IntPtr hwnd, uint fFlags);

        [PreserveSig]
        int SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
    }

    [Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), BestFitMapping(false)]
    public struct WIN32_FIND_DATAW
    {
        public uint dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    public static void OpenFolderAndSelectFiles(string folder, params string[] filesToSelect)
    {
        IntPtr dir = ILCreateFromPath(folder);

        var filesToSelectIntPtrs = new IntPtr[filesToSelect.Length];
        for (int i = 0; i < filesToSelect.Length; i++)
        {
            filesToSelectIntPtrs[i] = ILCreateFromPath(filesToSelect[i]);
        }

        SHOpenFolderAndSelectItems(dir, (uint) filesToSelect.Length, filesToSelectIntPtrs, 0);
        ReleaseComObject(dir);
        ReleaseComObject(filesToSelectIntPtrs);
    }

    private static void ReleaseComObject(params object[] comObjs)
    {
        foreach (object obj in comObjs)
        {
            if (obj != null && Marshal.IsComObject(obj))
                Marshal.ReleaseComObject(obj);
        }
    }
}

2

无法通过 explorer.exe 完成此操作。


你怎么知道的? - Paul Sumpner

2

根据您实际想要完成的任务,您可能可以使用AutoHotKey来完成。它是一个令人惊叹的免费工具,用于自动化通常无法完成的事情。它应该随Windows一起提供。当您按F12键时,此脚本将选择您的文件并突出显示下面的两个文件。

F12:: 
 run explorer.exe /select`, "c:\path\to\file.txt"
 SendInput {Shift Down}{Down}{Down}{Shift Up}
return

还有一种方法是将这两行中间的文本放入一个文本文件中,然后将其作为参数传递给autohotkey.exe。他们提供了一个编译脚本的选项,这将使它成为一个独立的exe,可以随时调用。带有详细帮助文档,使用起来非常方便。

@Orion,从C#中使用autohotkey也是可能的。您可以将autohotkey脚本制作成一个独立的可执行文件(大约400k),可以通过您的C#应用程序启动它(就像您启动资源管理器一样)。您还可以传递命令行参数。它不需要任何运行时要求。


如果文件不是彼此相邻的话,这不会很有效? - Svish
这就是为什么我说“取决于”和“可能能够”。Autohotkey中有许多选项,包括自动化搜索,我只是给出了一个例子。 - bruceatk
1
我不确定为什么这个被标记了。当你需要做一些无法使用现有API完成的事情时,AutoHotkey是一个很好的解决方案。它有许多保护措施可用于确保正确的程序被定位。有很多可能性。被标记为答案的回答并不是一个真正的回答。它只是一个显而易见的陈述,这也是提出问题的原因。我建议任何标记此答案的人都应该先了解AutoHotkey并学习它能做什么。它应该内置在Windows中。 - bruceatk
@bruceatk同意。点赞。RATM。可能有人过于追求细节,认为SO不喜欢你用库建议来解决问题,但这种类型的答案(特定上下文、示例代码和限制与能力的解释)完全符合要求,并适用于手头的问题。 - Max von Hippel

2

有COM自动化LateBinding IDispatch接口,这些接口可以轻松地从PowerShell、Visual Basic.NET和C#中使用,以下是一些示例代码:

$shell = New-Object -ComObject Shell.Application

function SelectFiles($filesToSelect)
{
    foreach ($fileToSelect in $filesToSelect)
    {
        foreach ($window in $shell.Windows())
        {
            foreach ($folderItem in $window.Document.Folder.Items())
            {
                if ($folderItem.Path -eq $fileToSelect)
                {
                    $window.Document.SelectItem($folderItem, 1 + 8)
                }
            }
        }
    }
}

-

Option Strict Off

Imports Microsoft.VisualBasic

Public Class ExplorerHelp
    Shared ShellApp As Object = CreateObject("Shell.Application")

    Shared Sub SelectFile(filepath As String)
        For Each i In ShellApp.Windows
            For Each i2 In i.Document.Folder.Items()
                If i2.Path = filepath Then
                    i.Document.SelectItem(i2, 1 + 8)
                    Exit Sub
                End If
            Next
        Next
    End Sub
End Class

https://learn.microsoft.com/en-us/windows/win32/shell/shellfolderview-selectitem


1
这是一个需要考虑你想要实现什么以及是否有更好的方法的问题。
为了提供更多背景信息,我们公司开发了一个C#客户端应用程序,允许用户加载文件并对其进行操作,就像iTunes管理您的MP3文件一样,而不会显示磁盘上的实际文件。
在应用程序中选择文件并执行“在Windows资源管理器中显示此文件”命令非常有用 - 这就是我想要实现的目标,并且已经针对单个文件完成了此操作。
我们有一个ListView,允许用户在应用程序中选择多个文件并移动/删除等。如果所有源文件都在同一个目录中,则最好能够使此“在Windows中显示此文件”命令适用于多个选定的文件,但如果不可能,则这不是一个主要功能。

将“在Windows中向我展示这个文件”替换为“显示在Windows文件夹中”(这里的“Windows”是可选的)--问题解决。 - jfs
@J.F.Sebastian 抱歉...您能详细解释一下吗?我不明白这如何解决OrionEdwards的问题...谢谢。 - Flak DiNenno
@FlakDiNenno:我的意思是(作为一个次优选项),你可以只打开包含文件的父文件夹(而不选择文件)。flashk的答案展示了如何打开文件夹并选择文件 - jfs
哦,我懂了。你并没有因为我简单地请求你解释更多而对我进行投票,是吗? - Flak DiNenno

0

我也想这样做。当你选择2个或多个文件并右键单击,然后选择“打开文件位置”时,媒体播放器可以实现此功能,但我不确定具体的实现方法(也不想花时间用procmon去找出答案)。


0

我想你可以使用FindWindowEx来获取Windows资源管理器的SysListView32,然后使用SendMessageLVM_SETITEMSTATE选择项目。难点在于知道项目的位置...也许可以使用LVM_FINDITEM解决这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接