Voici une version purement C#, sans nuget, qui devrait fonctionner avec toutes les versions de .NET (y compris .NET Core, .NET 5, WPF, Winforms, etc.) et utiliser Windows Vista (et supérieur) IFileDialog avec l'interface FOS_PICKFOLDERS de sorte qu'il dispose de l'interface standard Windows avec un sélecteur de dossiers.
J'ai également ajouté la fonction de WPF Window
mais cela est facultatif, les lignes marquées par WPF peuvent simplement être supprimées.
l'usage :
var dlg = new FolderPicker();
dlg.InputPath = @"c:\windows\system32";
if (dlg.ShowDialog() == true)
{
MessageBox.Show(dlg.ResultPath);
}
code :
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows; // for WPF support
using System.Windows.Interop; // for WPF support
public class FolderPicker
{
public virtual string ResultPath { get; protected set; }
public virtual string ResultName { get; protected set; }
public virtual string InputPath { get; set; }
public virtual bool ForceFileSystem { get; set; }
public virtual string Title { get; set; }
public virtual string OkButtonLabel { get; set; }
public virtual string FileNameLabel { get; set; }
protected virtual int SetOptions(int options)
{
if (ForceFileSystem)
{
options |= (int)FOS.FOS_FORCEFILESYSTEM;
}
return options;
}
// for WPF support
public bool? ShowDialog(Window owner = null, bool throwOnError = false)
{
owner ??= Application.Current.MainWindow;
return ShowDialog(owner != null ? new WindowInteropHelper(owner).Handle : IntPtr.Zero, throwOnError);
}
// for all .NET
public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false)
{
var dialog = (IFileOpenDialog)new FileOpenDialog();
if (!string.IsNullOrEmpty(InputPath))
{
if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out var item), throwOnError) != 0)
return null;
dialog.SetFolder(item);
}
var options = FOS.FOS_PICKFOLDERS;
options = (FOS)SetOptions((int)options);
dialog.SetOptions(options);
if (Title != null)
{
dialog.SetTitle(Title);
}
if (OkButtonLabel != null)
{
dialog.SetOkButtonLabel(OkButtonLabel);
}
if (FileNameLabel != null)
{
dialog.SetFileName(FileNameLabel);
}
if (owner == IntPtr.Zero)
{
owner = Process.GetCurrentProcess().MainWindowHandle;
if (owner == IntPtr.Zero)
{
owner = GetDesktopWindow();
}
}
var hr = dialog.Show(owner);
if (hr == ERROR_CANCELLED)
return null;
if (CheckHr(hr, throwOnError) != 0)
return null;
if (CheckHr(dialog.GetResult(out var result), throwOnError) != 0)
return null;
if (CheckHr(result.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError) != 0)
return null;
ResultPath = path;
if (CheckHr(result.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out path), false) == 0)
{
ResultName = path;
}
return true;
}
private static int CheckHr(int hr, bool throwOnError)
{
if (hr != 0)
{
if (throwOnError)
Marshal.ThrowExceptionForHR(hr);
}
return hr;
}
[DllImport("shell32")]
private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
[DllImport("user32")]
private static extern IntPtr GetDesktopWindow();
#pragma warning disable IDE1006 // Naming Styles
private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
#pragma warning restore IDE1006 // Naming Styles
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog
private class FileOpenDialog
{
}
[ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IFileOpenDialog
{
[PreserveSig] int Show(IntPtr parent); // IModalWindow
[PreserveSig] int SetFileTypes(); // not fully defined
[PreserveSig] int SetFileTypeIndex(int iFileType);
[PreserveSig] int GetFileTypeIndex(out int piFileType);
[PreserveSig] int Advise(); // not fully defined
[PreserveSig] int Unadvise();
[PreserveSig] int SetOptions(FOS fos);
[PreserveSig] int GetOptions(out FOS pfos);
[PreserveSig] int SetDefaultFolder(IShellItem psi);
[PreserveSig] int SetFolder(IShellItem psi);
[PreserveSig] int GetFolder(out IShellItem ppsi);
[PreserveSig] int GetCurrentSelection(out IShellItem ppsi);
[PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
[PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
[PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
[PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
[PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
[PreserveSig] int GetResult(out IShellItem ppsi);
[PreserveSig] int AddPlace(IShellItem psi, int alignment);
[PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
[PreserveSig] int Close(int hr);
[PreserveSig] int SetClientGuid(); // not fully defined
[PreserveSig] int ClearClientData();
[PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter);
[PreserveSig] int GetResults([MarshalAs(UnmanagedType.IUnknown)] out object ppenum);
[PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai);
}
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItem
{
[PreserveSig] int BindToHandler(); // not fully defined
[PreserveSig] int GetParent(); // not fully defined
[PreserveSig] int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
[PreserveSig] int GetAttributes(); // not fully defined
[PreserveSig] int Compare(); // not fully defined
}
#pragma warning disable CA1712 // Do not prefix enum values with type name
private enum SIGDN : uint
{
SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
SIGDN_FILESYSPATH = 0x80058000,
SIGDN_NORMALDISPLAY = 0,
SIGDN_PARENTRELATIVE = 0x80080001,
SIGDN_PARENTRELATIVEEDITING = 0x80031001,
SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
SIGDN_PARENTRELATIVEPARSING = 0x80018001,
SIGDN_URL = 0x80068000
}
[Flags]
private enum FOS
{
FOS_OVERWRITEPROMPT = 0x2,
FOS_STRICTFILETYPES = 0x4,
FOS_NOCHANGEDIR = 0x8,
FOS_PICKFOLDERS = 0x20,
FOS_FORCEFILESYSTEM = 0x40,
FOS_ALLNONSTORAGEITEMS = 0x80,
FOS_NOVALIDATE = 0x100,
FOS_ALLOWMULTISELECT = 0x200,
FOS_PATHMUSTEXIST = 0x800,
FOS_FILEMUSTEXIST = 0x1000,
FOS_CREATEPROMPT = 0x2000,
FOS_SHAREAWARE = 0x4000,
FOS_NOREADONLYRETURN = 0x8000,
FOS_NOTESTFILECREATE = 0x10000,
FOS_HIDEMRUPLACES = 0x20000,
FOS_HIDEPINNEDPLACES = 0x40000,
FOS_NODEREFERENCELINKS = 0x100000,
FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
FOS_DONTADDTORECENT = 0x2000000,
FOS_FORCESHOWHIDDEN = 0x10000000,
FOS_DEFAULTNOMINIMODE = 0x20000000,
FOS_FORCEPREVIEWPANEON = 0x40000000,
FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000)
}
#pragma warning restore CA1712 // Do not prefix enum values with type name
}
résultat :
0 votes
Si vous ouvrez le fichier du projet avec l'éditeur, vous remarquerez quelques propriétés supplémentaires en bas : <VCIncludePath ...>, <Win32Resource ...> et <Target Name="BeforeBuild"...>. Vous verrez qu'il exécute rc.exe pour compiler le fichier ressource res1.rc (assurez-vous de copier également le fichier "resource.h" dans votre projet). Assurez-vous que vous avez installé VisualC et que VCIncludePath pointe vers un emplacement approprié (celui de github pointe vers la version VC9.0, et vous devrez peut-être le changer). Après avoir compilé le fichier .rc, le fichier .res résultant est ajouté comme ressource pour votre exécutable avec la directive Win32Resource.
3 votes
Il existe une solution bricolée utilisant OpenFileDialog où
ValidateNames
yCheckFileExists
sont tous deux mis à faux etFileName
reçoit une valeur fictive pour indiquer qu'un répertoire est sélectionné. Je dis "hack" parce que cela prête à confusion pour les utilisateurs sur la façon de sélectionner un répertoire. Voir Sélectionnez un fichier ou un dossier dans la même boîte de dialogue0 votes
Duplicata possible de Comment configurer un OpenFileDialog pour sélectionner des dossiers ?
1 votes
Merci à Dan de m'avoir indiqué la solution OpenFileDialog ! C'est bien mieux que FolderBrowserDialog, parce que OFD montre les dossiers marqués d'un signet, etc, de sorte que tout le monde - en particulier dans les grandes entreprises - trouve leur merde. FBD ne sera pas très utile dans ces endroits.
0 votes
@DanielBallinger Je suis vraiment intéressé par le fonctionnement de votre méthode, mais lorsque j'ouvre la boîte de dialogue, que je sélectionne un dossier, que je clique sur ouvrir, puis que j'ouvre une deuxième fois, j'obtiens une boîte de dialogue d'erreur disant "fichier non trouvé", je ne sais pas pourquoi j'essaie de trouver le fichier après que CheckFileExists ait été réglé sur false.
1 votes
@ComradeJoecool J'ai converti mon commentaire en une réponse . Je l'ai essayé plusieurs fois et je n'ai pas eu de problème de "fichier non trouvé". Réutilisez-vous la même instance d'OpenFileDialog ?
1 votes
@DanielBallinger ah, j'ai trouvé mon problème, puisque j'utilise Powershell pour créer la boîte de dialogue, en paramétrant
ValidateNames
yCheckFileExists
àfalse
ne fonctionnait pas, j'avais besoin de les définir en tant que0
(ou mieux apprendre powershell)0 votes
Je pense que ce paquet nuget est un bon choix. BetterFolderBrowser
0 votes
Je voudrais fortement proposer de changer la réponse sélectionnée La réponse de Simon Mourier comme la réponse acceptée. J'ai mis en œuvre sa solution (après l'avoir trouvée ailleurs, j'ai découvert qu'il l'avait postée ici il y a quelques jours seulement), et elle fonctionne étonnamment bien.