OpenFolderDialog

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.]

16 thoughts to “OpenFolderDialog”

    1. Added
      public const uint OFN_ALLOWMULTISELECT = 0x00000200;
      But failed in
      if (frm.GetResult(out shellItem) == NativeMethods.S_OK)

  1. 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?

  2. Here is the vb.net version

    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
            Get
                Return m_InitialFolder
            End Get
            Set(ByVal value As String)
                m_InitialFolder = value
            End Set
        End Property
        Private m_InitialFolder As String
    
        ''' 
        ''' Gets/sets directory in which dialog will be open if there is no recent directory available.
        ''' 
        Public Property DefaultFolder() As String
            Get
                Return m_DefaultFolder
            End Get
            Set(ByVal value As String)
                m_DefaultFolder = value
            End Set
        End Property
        Private m_DefaultFolder As String
    
        ''' 
        ''' Gets selected folder.
        ''' 
        Public Property Folder() As String
            Get
                Return m_Folder
            End Get
            Private Set(ByVal value As String)
                m_Folder = value
            End Set
        End Property
        Private m_Folder As String
    
    
        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
            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
                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
                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
                If frm.GetResult(shellItem) = NativeMethods.S_OK Then
                    Dim pszString As IntPtr
                    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
    
         _
         Public Function SHCreateItemFromParsingName( _
              ByVal pszPath As String, _
             ByVal pbc As IntPtr, _
              ByVal riid As Guid, _
              ByRef ppv As IShellItem) As Integer
        End Function
    End Module
    
    
    
    1. 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.

      1. 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

Leave a Reply

Your email address will not be published. Required fields are marked *