WPF 打開(kāi)資源管理器且選中某個(gè)文件
命令行方法
打開(kāi)資源管理器且選中某個(gè)文件可以使用 cmd 調(diào)用 explorer 帶上 select 參數(shù),如下面命令行所示
explorer.exe /select,"C:\Folder\file.txt"
但有很多情況下,用戶可能使用其他資源管理器,此時(shí)將會(huì)導(dǎo)致應(yīng)用軟件打開(kāi)的是 explorer 而不是用戶默認(rèn)的資源管理器
SHOpenFolderAndSelectItems 單文件
通過(guò) shell32.dll 提供的 SHOpenFolderAndSelectItems 方法,可以直接使用函數(shù)調(diào)用的方式打開(kāi)資源管理器且選中某個(gè)文件,且使用的是用戶設(shè)置的默認(rèn)的資源管理器
以下是我創(chuàng)建的簡(jiǎn)單的 WPF 例子程序的界面,可以看到界面非常簡(jiǎn)單,就是輸入一個(gè)文件,然后點(diǎn)擊按鈕就可以打開(kāi)資源管理器選中輸入的文件
<Grid>
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="文件路徑:" Margin="50,0,0,0" VerticalAlignment="Center"/>
<TextBox x:Name="InputTextBox" Grid.Column="1" Margin="10,0,10,0" VerticalAlignment="Center"/>
<Button Grid.Column="2" Content="打開(kāi)" Margin="10,0,50,0" VerticalAlignment="Center" Click="Button_OnClick"/>
</Grid>
</Grid>
按鈕的后臺(tái)代碼將需要使用 PInvoke 調(diào)用 Win32 函數(shù)。對(duì)于 dotnet 7 以前的程序,可使用如下方式定義
[DllImport("shell32.dll", ExactSpelling = true)]
private static extern void ILFree(IntPtr pidlList);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll", ExactSpelling = true)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
對(duì)于 dotnet 7 以及更高版本的項(xiàng)目,可使用 LibraryImportAttribute 特性輔助定義。如以下 C# 代碼所示
[LibraryImport("shell32.dll")]
private static partial void ILFree(IntPtr pidlList);
[LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
private static partial IntPtr ILCreateFromPathW(string pszPath);
[LibraryImport("shell32.dll")]
private static partial int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
過(guò)程中別忘了在 csproj 項(xiàng)目文件里面開(kāi)啟不安全代碼,開(kāi)啟之后的項(xiàng)目文件代碼大概如下
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
后臺(tái) C# 代碼的按鈕點(diǎn)擊事件里面將調(diào)用 SHOpenFolderAndSelectItems 方法打開(kāi)資源管理器選中輸入的文件
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var filePath = InputTextBox.Text;
filePath = System.IO.Path.GetFullPath(filePath);
IntPtr pidlList = ILCreateFromPathW(filePath);
if (pidlList != IntPtr.Zero)
{
try
{
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
}
finally
{
ILFree(pidlList);
}
}
}
以上代碼里面的 ILCreateFromPathW 要求傳入絕對(duì)路徑,需要調(diào)用 System.IO.Path.GetFullPath 方法轉(zhuǎn)換傳入路徑為絕對(duì)路徑
如果不知道代碼如何寫的話,可以拉取我的例子項(xiàng)目代碼跑跑看
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個(gè)代碼倉(cāng)庫(kù)比較龐大,使用以下命令行可以進(jìn)行部分拉取,拉取速度比較快
先創(chuàng)建一個(gè)空文件夾,接著使用命令行 cd 命令進(jìn)入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6988631e41226832c3b83cf62529eb7d7892e0b2
以上使用的是國(guó)內(nèi)的 gitee 的源,如果 gitee 不能訪問(wèn),請(qǐng)?zhí)鎿Q為 github 的源。請(qǐng)?jiān)诿钚欣^續(xù)輸入以下代碼,將 gitee 源換成 github 源進(jìn)行拉取代碼。如果依然拉取不到代碼,可以發(fā)郵件向我要代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 6988631e41226832c3b83cf62529eb7d7892e0b2
獲取代碼之后,進(jìn)入 WPFDemo/WilinojearcheWheyecearhire 文件夾,即可獲取到源代碼
更多一些細(xì)節(jié)信息是調(diào)用 SHOpenFolderAndSelectItems 之前請(qǐng)確保已經(jīng)初始化 COM 組件,即調(diào)用過(guò) CoInitialize 方法。在 WPF 里面為了和 DirectX 等交互,在按鈕點(diǎn)擊之前就已經(jīng)調(diào)研過(guò)了 COM 初始化了,因此在 WPF 里面可以省略此邏輯。但是在控制臺(tái)應(yīng)用里面,需要手動(dòng)調(diào)用一下,代碼如下
CoInitialize(0, 0);
[LibraryImport("Ole32.dll")]
private static partial int CoInitialize(IntPtr pvReserved, uint dwCoInit);
我再次更新 WPF 例子項(xiàng)目的代碼,在按鈕點(diǎn)擊的方法里面調(diào)用。不過(guò)在按鈕點(diǎn)擊方法里面調(diào)用是必然返回失敗的,如上文所述,這是因?yàn)?WPF 早已初始化過(guò)了。好在這個(gè)方法失敗了也沒(méi)有什么問(wèn)題,可以放心調(diào)用
private void Button_OnClick(object sender, RoutedEventArgs e)
{
// 必定返回失敗,因?yàn)?WPF 已經(jīng)調(diào)用過(guò)了
var result = CoInitialize(0, 0);
... // 忽略其他代碼
}
根據(jù) SHOpenFolderAndSelectItems 文檔如下描述,如果沒(méi)有先調(diào)用 CoInitialize 則會(huì)失敗
CoInitialize or CoInitializeEx must be called before using SHOpenFolderAndSelectItems. Not doing so causes SHOpenFolderAndSelectItems to fail.
SHOpenFolderAndSelectItems 選中多個(gè)文件
有時(shí)候咱的需求是打開(kāi)文件夾,選中里面多個(gè)文件,此時(shí)依然可以使用 SHOpenFolderAndSelectItems 方法,只是咱需要修改一下剛才的函數(shù)簽名。修改之后的代碼如下
// 修改前:
[LibraryImport("shell32.dll")]
private static partial int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
// 修改后:
[LibraryImport("shell32.dll")]
private static partial int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] children, uint dwFlags);
可以看到修改后的差別只是將 children 參數(shù)的類型修改為 IntPtr[] 指針數(shù)組類型,且標(biāo)記了作為 LPArray 方式傳入而已。如果不想改的話,那也可以自己使用 System.Runtime.InteropServices.Marshalling.ArrayMarshaller<nint, nint>.ManagedToUnmanagedIn 的 GetPinnableReference 方法將指針數(shù)組轉(zhuǎn)換為指針傳入。只不過(guò)此時(shí)的指針對(duì)應(yīng)在 C 的定義是指針的指針而已
選中多個(gè)文件的使用方法就是在 pidlList 參數(shù)傳入多個(gè)文件所在的文件夾,在 children 參數(shù)里面?zhèn)魅胄枰x中的文件
傳入的這些路徑都需要經(jīng)過(guò) ILCreateFromPathW 處理,以下是我修改之后的按鈕點(diǎn)擊事件代碼,可以全選文件夾里面的所有文件
private void Button_OnClick(object sender, RoutedEventArgs e)
{
// 必定返回失敗,因?yàn)?WPF 已經(jīng)調(diào)用過(guò)了
var result = CoInitialize(0, 0);
var folderPath = InputTextBox.Text;
folderPath = System.IO.Path.GetFullPath(folderPath);
IntPtr pidlList = ILCreateFromPathW(folderPath);
if (pidlList != IntPtr.Zero)
{
var fileList = Directory.GetFiles(folderPath);
var selectedFileList = new IntPtr[fileList.Length];
for (var i = 0; i < fileList.Length; i++)
{
var file = fileList[i];
selectedFileList[i] = ILCreateFromPathW(file);
}
try
{
// Open parent folder and select item
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, (uint) fileList.Length, selectedFileList, 0));
}
finally
{
ILFree(pidlList);
foreach (var nint in selectedFileList)
{
ILFree(nint);
}
}
}
}
[LibraryImport("Ole32.dll")]
private static partial int CoInitialize(IntPtr pvReserved, uint dwCoInit);
[LibraryImport("shell32.dll")]
private static partial void ILFree(IntPtr pidlList);
[LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
private static partial IntPtr ILCreateFromPathW(string pszPath);
[LibraryImport("shell32.dll")]
private static partial int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] children, uint dwFlags);
嘗試替換以上代碼到項(xiàng)目里,運(yùn)行項(xiàng)目即可進(jìn)行測(cè)試打開(kāi)資源管理器選中輸入的文件夾的所有文件
那如果需要選中多個(gè)文件夾呢?自然只需將以上代碼的 fileList 替換為文件夾列表就可以了。在 SHOpenFolderAndSelectItems 的 children 參數(shù)里面可以的內(nèi)容是傳入文件或文件夾。可以混合多選多個(gè)文件和文件夾同時(shí)
參考文檔
c# - How to open Explorer with a specific file selected? - Stack Overflow
file - C#: How to use SHOpenFolderAndSelectItems - Stack Overflow
c#: 打開(kāi)文件夾并選中文件 - 楚人無(wú)衣 - 博客園
SHOpenFolderAndSelectItems 函數(shù) (shlobj_core.h) - Win32 apps - Microsoft Learn
【C#】在Windows資源管理器打開(kāi)文件夾,并選中指定的文件或文件夾 - Tod's - 博客園
CoInitializeEx 函數(shù) (combaseapi.h) - Win32 apps Microsoft Learn
SHOpenFolderAndSelectItems function (shlobj_core.h) - Win32 apps Microsoft Learn
更多 WPF 博客,請(qǐng)參閱 博客導(dǎo)航
博客園博客只做備份,博客發(fā)布就不再更新,如果想看最新博客,請(qǐng)?jiān)L問(wèn) https://blog.lindexi.com/
如圖片看不見(jiàn),請(qǐng)?jiān)跒g覽器開(kāi)啟不安全http內(nèi)容兼容

本作品采用知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議進(jìn)行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但務(wù)必保留文章署名[林德熙](http://www.rzrgm.cn/lindexi)(包含鏈接:http://www.rzrgm.cn/lindexi ),不得用于商業(yè)目的,基于本文修改后的作品務(wù)必以相同的許可發(fā)布。如有任何疑問(wèn),請(qǐng)與我[聯(lián)系](mailto:lindexi_gd@163.com)。

浙公網(wǎng)安備 33010602011771號(hào)