I hate FolderBrowserDialog
. It is limited, ugly and stuck in Windows 2000 GUI. Whenever I had to select folder, I would resort to using SaveFileDialog
instead. User would select any file name and I would just use directory part. It was non-optimal but still better than FolderBrowserDialog
.
Vista brought new common file dialogs but I pretty much ignored them - most of my programs need to run under XP so there was not much benefit in using Windows Vista specific controls. However, those controls had special mode that finally brings folder selection in normal FileOpenDialog
.
Code (after you define all needed COM interfaces) is actually quite straightforward. Once FOS_PICKFOLDERS is set, half of work is done. All other code is just plumbing to show the form and get user's selection in form of path.
var frm = (IFileDialog)(new FileOpenDialogRCW());
uint options;
frm.GetOptions(out options);
options |= FOS_PICKFOLDERS;
frm.SetOptions(options);
if (frm.Show(owner.Handle) == S_OK) {
IShellItem shellItem;
frm.GetResult(out shellItem);
IntPtr pszString;
shellItem.GetDisplayName(SIGDN_FILESYSPATH, out pszString);
this.Folder = Marshal.PtrToStringAuto(pszString);
}
Full code with Windows XP compatibility and little bit more error checking is available for download. However do notice that all unneeded COM fat has been trimmed in order to have it concise. Short and sweet I say.
[2012-02-12: Fixed bug in source code.]
It crashes when you press cancel.
That is exercise left for a reader. Hint: I forgot PreserveSig.
what references do I need?
Check it in source.
Work fine! How to pick multifolder?
Added
public const uint OFN_ALLOWMULTISELECT = 0x00000200;
But failed in
if (frm.GetResult(out shellItem) == NativeMethods.S_OK)
Is there a vb.net version of this?
Not currently.
Works good with Windows 7 but with XP it just opens the standard SaveFileDialog showing the box for the file name and even [Save] on the button.
I thought this was supposed to work on XP too?
XP does not have new common file dialogs.
Program will work but it will fallback to standard save file dialog.
What is the licensing for this code?
Licence is MIT – in short, do whatever you like with it.
Of course, if you like it a lot or you include it in a product, I won’t say no to a small donation but it is definitely not required.
Thanks!
This is exactly what I was looking for! :)
Here is the vb.net version
I’m getting an error on the line
Dim frm = DirectCast(New NativeMethods.FileOpenDialogRCW(), NativeMethods.IFileDialog)
“Unable to cast object of type ‘FileOpenDialogRCW’ to type ‘IFileDialog’.”
I don’t know what to do to fix it.
Hi Brian,
Following code works better… just checked few minutes ago
Option Infer On
‘Copyright (c) 2011 Josip Medved http://www.jmedved.com
Imports System
Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Public Class OpenFolderDialog
Implements IDisposable
”’
”’ Gets/sets folder in which dialog will be open.
”’
Public Property InitialFolder() As String
”’
”’ Gets/sets directory in which dialog will be open if there is no recent directory available.
”’
Public Property DefaultFolder() As String
”’
”’ Gets selected folder.
”’
Private privateFolder As String
Public Property Folder() As String
Get
Return privateFolder
End Get
Private Set(ByVal value As String)
privateFolder = value
End Set
End Property
Public Function ShowDialog(ByVal owner As IWin32Window) As DialogResult
If Environment.OSVersion.Version.Major >= 6 Then
Return ShowVistaDialog(owner)
Else
Return ShowLegacyDialog(owner)
End If
End Function
Private Function ShowVistaDialog(ByVal owner As IWin32Window) As DialogResult
Dim frm = DirectCast(New NativeMethods.FileOpenDialogRCW(), NativeMethods.IFileDialog)
Dim options As UInteger = Nothing
frm.GetOptions(options)
options = options Or NativeMethods.FOS_PICKFOLDERS Or NativeMethods.FOS_FORCEFILESYSTEM Or NativeMethods.FOS_NOVALIDATE Or NativeMethods.FOS_NOTESTFILECREATE Or NativeMethods.FOS_DONTADDTORECENT
frm.SetOptions(options)
If Me.InitialFolder IsNot Nothing Then
Dim directoryShellItem As NativeMethods.IShellItem = Nothing
Dim riid = New Guid(“43826D1E-E718-42EE-BC55-A1E261C37BFE”) ‘IShellItem
If NativeMethods.SHCreateItemFromParsingName(Me.InitialFolder, IntPtr.Zero, riid, directoryShellItem) = NativeMethods.S_OK Then
frm.SetFolder(directoryShellItem)
End If
End If
If Me.DefaultFolder IsNot Nothing Then
Dim directoryShellItem As NativeMethods.IShellItem = Nothing
Dim riid = New Guid(“43826D1E-E718-42EE-BC55-A1E261C37BFE”) ‘IShellItem
If NativeMethods.SHCreateItemFromParsingName(Me.DefaultFolder, IntPtr.Zero, riid, directoryShellItem) = NativeMethods.S_OK Then
frm.SetDefaultFolder(directoryShellItem)
End If
End If
If frm.Show(owner.Handle) = NativeMethods.S_OK Then
Dim shellItem As NativeMethods.IShellItem = Nothing
If frm.GetResult(shellItem) = NativeMethods.S_OK Then
Dim pszString As IntPtr = Nothing
If shellItem.GetDisplayName(NativeMethods.SIGDN_FILESYSPATH, pszString) = NativeMethods.S_OK Then
If pszString IntPtr.Zero Then
Try
Me.Folder = Marshal.PtrToStringAuto(pszString)
Return DialogResult.OK
Finally
Marshal.FreeCoTaskMem(pszString)
End Try
End If
End If
End If
End If
Return DialogResult.Cancel
End Function
Private Function ShowLegacyDialog(ByVal owner As IWin32Window) As DialogResult
Using frm = New SaveFileDialog()
frm.CheckFileExists = False
frm.CheckPathExists = True
frm.CreatePrompt = False
frm.Filter = “|” & Guid.Empty.ToString()
frm.FileName = “any”
If Me.InitialFolder IsNot Nothing Then
frm.InitialDirectory = Me.InitialFolder
End If
frm.OverwritePrompt = False
frm.Title = “Select Folder”
frm.ValidateNames = False
If frm.ShowDialog(owner) = DialogResult.OK Then
Me.Folder = Path.GetDirectoryName(frm.FileName)
Return DialogResult.OK
Else
Return DialogResult.Cancel
End If
End Using
End Function
Public Sub Dispose() Implements IDisposable.Dispose
End Sub ‘just to have possibility of Using statement.
End Class
Friend Module NativeMethods
#Region “Constants”
Public Const FOS_PICKFOLDERS As UInteger = &H20
Public Const FOS_FORCEFILESYSTEM As UInteger = &H40
Public Const FOS_NOVALIDATE As UInteger = &H100
Public Const FOS_NOTESTFILECREATE As UInteger = &H10000
Public Const FOS_DONTADDTORECENT As UInteger = &H2000000
Public Const S_OK As UInteger = &H0
Public Const SIGDN_FILESYSPATH As UInteger = &H80058000UI
#End Region
#Region “COM”
Friend Class FileOpenDialogRCW
End Class
Friend Interface IFileDialog
Function Show( ByVal hwndOwner As IntPtr) As UInteger ‘IModalWindow
Function SetFileTypes( ByVal cFileTypes As UInteger, ByVal rgFilterSpec As IntPtr) As UInteger
Function SetFileTypeIndex( ByVal iFileType As UInteger) As UInteger
Function GetFileTypeIndex(ByRef piFileType As UInteger) As UInteger
Function Advise( ByVal pfde As IntPtr, ByRef pdwCookie As UInteger) As UInteger
Function Unadvise( ByVal dwCookie As UInteger) As UInteger
Function SetOptions( ByVal fos As UInteger) As UInteger
Function GetOptions(ByRef fos As UInteger) As UInteger
Sub SetDefaultFolder( ByVal psi As IShellItem)
Function SetFolder( ByVal psi As IShellItem) As UInteger
Function GetFolder( ByRef ppsi As IShellItem) As UInteger
Function GetCurrentSelection( ByRef ppsi As IShellItem) As UInteger
Function SetFileName( ByVal pszName As String) As UInteger
Function GetFileName( ByRef pszName As String) As UInteger
Function SetTitle( ByVal pszTitle As String) As UInteger
Function SetOkButtonLabel( ByVal pszText As String) As UInteger
Function SetFileNameLabel( ByVal pszLabel As String) As UInteger
Function GetResult( ByRef ppsi As IShellItem) As UInteger
Function AddPlace( ByVal psi As IShellItem, ByVal fdap As UInteger) As UInteger
Function SetDefaultExtension( ByVal pszDefaultExtension As String) As UInteger
Function Close( ByVal hr As UInteger) As UInteger
Function SetClientGuid( ByRef guid As Guid) As UInteger
Function ClearClientData() As UInteger
Function SetFilter( ByVal pFilter As IntPtr) As UInteger
End Interface
Friend Interface IShellItem
Function BindToHandler( ByVal pbc As IntPtr, ByRef rbhid As Guid, ByRef riid As Guid, ByRef ppvOut As IntPtr) As UInteger
Function GetParent( ByRef ppsi As IShellItem) As UInteger
Function GetDisplayName( ByVal sigdnName As UInteger, ByRef ppszName As IntPtr) As UInteger
Function GetAttributes( ByVal sfgaoMask As UInteger, ByRef psfgaoAttribs As UInteger) As UInteger
Function Compare( ByVal psi As IShellItem, ByVal hint As UInteger, ByRef piOrder As Integer) As UInteger
End Interface
#End Region
Friend Function SHCreateItemFromParsingName( ByVal pszPath As String, ByVal pbc As IntPtr, ByRef riid As Guid, ByRef ppv As IShellItem) As Integer
End Function
End Module