Click here to Skip to main content
14,422,431 members

C# Select FolderDialog for .NET Core 3.0

Rate this:
3.15 (3 votes)
Please Sign up or sign in to vote.
3.15 (3 votes)
7 Jan 2020CPOL
C# .NET Core Folder Dialog box example that uses the "Explorer" Open File Dialog Box

Introduction

For modern day desktop Windows Form applications needing to be migrated from .NET Framework to .NET Core, this article discusses the implementation of one component for a Windows Form, the Folder Dialog box. This component allows users to cleanly select folder destinations with the modern look and feel of the Open File Dialog box. This is compatible with .NET Framework as well.

Folder Dialog Box preview

Console Output

Background

This demonstration will consist of running a console application initiating an Open Folder Dialog Box, allowing the user to select a folder, and then displaying the folder path in the console window. I did this in a console application to demonstrate ease of implementation.

Using the Code

This code example is a layered approach. The first layer is the Assemblies layer. It is necessary to have these Microsoft .NET Core DLLs imported as a reference to your Business Logic Layer (BLL) and Console layer. The idea behind the development pattern is to allow the Console to demonstrate the Folder Dialog box, and the BLL layer can be repurposed into any other project.

Assemblies Layer

Consists of the following DLLs:

  • System.Drawing.Dll
  • System.Windows.Forms.dll

** These DLLs can be found on your system at the following location: C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\3.0.0 **

Business Layer

A .NET Core Class Library with a class and interface files, Select.cs and ISelect.cs.

Select.cs

using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace FolderDialog.Bll.FolderDialog
{
    public class Select : ISelect
    {
        /// <summary>
        /// Gets/sets folder in which dialog will be open.
        /// </summary>
        public string InitialFolder { get; set; }

        /// <summary>
        /// Gets/sets directory in which dialog will be open 
        /// if there is no recent directory available.
        /// </summary>
        public string DefaultFolder { get; set; }

        /// <summary>
        /// Gets selected folder.
        /// </summary>
        public string Folder { get; set; }
        public DialogResult ShowDialog()
        {
            return ShowDialog(owner: new WindowWrapper(IntPtr.Zero));
        }
        public DialogResult ShowDialog(IWin32Window owner)
        {
            if (Environment.OSVersion.Version.Major >= 6)
            {
                return ShowVistaDialog(owner);
            }
            else
            {
                return ShowLegacyDialog(owner);
            }
        }
        public DialogResult ShowVistaDialog(IWin32Window owner)
        {
            var frm = (NativeMethods.IFileDialog)(new NativeMethods.FileOpenDialogRCW());
            uint options;
            frm.GetOptions(out options);
            options |= NativeMethods.FOS_PICKFOLDERS | 
                       NativeMethods.FOS_FORCEFILESYSTEM | 
                       NativeMethods.FOS_NOVALIDATE | 
                       NativeMethods.FOS_NOTESTFILECREATE | 
                       NativeMethods.FOS_DONTADDTORECENT;
            frm.SetOptions(options);
            if (this.InitialFolder != null)
            {
                NativeMethods.IShellItem directoryShellItem;
                var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem
                if (NativeMethods.SHCreateItemFromParsingName
                   (this.InitialFolder, IntPtr.Zero, ref riid, 
                    out directoryShellItem) == NativeMethods.S_OK)
                {
                    frm.SetFolder(directoryShellItem);
                }
            }
            if (this.DefaultFolder != null)
            {
                NativeMethods.IShellItem directoryShellItem;
                var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem
                if (NativeMethods.SHCreateItemFromParsingName
                   (this.DefaultFolder, IntPtr.Zero, ref riid, 
                    out directoryShellItem) == NativeMethods.S_OK)
                {
                    frm.SetDefaultFolder(directoryShellItem);
                }
            }

            if (frm.Show(owner.Handle) == NativeMethods.S_OK)
            {
                NativeMethods.IShellItem shellItem;
                if (frm.GetResult(out shellItem) == NativeMethods.S_OK)
                {
                    IntPtr pszString;
                    if (shellItem.GetDisplayName(NativeMethods.SIGDN_FILESYSPATH, 
                        out pszString) == NativeMethods.S_OK)
                    {
                        if (pszString != IntPtr.Zero)
                        {
                            try
                            {
                                this.Folder = Marshal.PtrToStringAuto(pszString);
                                return DialogResult.OK;
                            }
                            finally
                            {
                                Marshal.FreeCoTaskMem(pszString);
                            }
                        }
                    }
                }
            }
            return DialogResult.Cancel;
        }
        public DialogResult ShowLegacyDialog(IWin32Window owner)
        {
            using (var frm = new SaveFileDialog())
            {
                frm.CheckFileExists = false;
                frm.CheckPathExists = true;
                frm.CreatePrompt = false;
                frm.Filter = "|" + Guid.Empty.ToString();
                frm.FileName = "any";
                if (this.InitialFolder != null) { frm.InitialDirectory = this.InitialFolder; }
                frm.OverwritePrompt = false;
                frm.Title = "Select Folder";
                frm.ValidateNames = false;
                if (frm.ShowDialog(owner) == DialogResult.OK)
                {
                    this.Folder = Path.GetDirectoryName(frm.FileName);
                    return DialogResult.OK;
                }
                else
                {
                    return DialogResult.Cancel;
                }
            }
        }
        public void Dispose() { } //just to have possibility of Using statement.
    }
    public class WindowWrapper : System.Windows.Forms.IWin32Window
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="handle">Handle to wrap</param>
        public WindowWrapper(IntPtr handle)
        {
            _hwnd = handle;
        }

        /// <summary>
        /// Original ptr
        /// </summary>
        public IntPtr Handle
        {
            get { return _hwnd; }
        }

        private IntPtr _hwnd;
    }
    internal static class NativeMethods
    {
        #region Constants

        public const uint FOS_PICKFOLDERS = 0x00000020;
        public const uint FOS_FORCEFILESYSTEM = 0x00000040;
        public const uint FOS_NOVALIDATE = 0x00000100;
        public const uint FOS_NOTESTFILECREATE = 0x00010000;
        public const uint FOS_DONTADDTORECENT = 0x02000000;

        public const uint S_OK = 0x0000;

        public const uint SIGDN_FILESYSPATH = 0x80058000;

        #endregion

        #region COM

        [ComImport, ClassInterface(ClassInterfaceType.None), 
        TypeLibType(TypeLibTypeFlags.FCanCreate), 
                    Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
        internal class FileOpenDialogRCW { }

        [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), 
                      InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IFileDialog
        {
            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            [PreserveSig()]
            uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow 

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetFileTypes([In] uint cFileTypes, 
            [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetFileTypeIndex([In] uint iFileType);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetFileTypeIndex(out uint piFileType);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, 
            out uint pdwCookie);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint Unadvise([In] uint dwCookie);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetOptions([In] uint fos);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetOptions(out uint fos);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetCurrentSelection
                 ([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint AddPlace
              ([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap);

            [MethodImpl(MethodImplOptions.InternalCall, 
             MethodCodeType = MethodCodeType.Runtime)]
            uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] 
                                     string pszDefaultExtension);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint Close([MarshalAs(UnmanagedType.Error)] uint hr);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint SetClientGuid([In] ref Guid guid);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint ClearClientData();

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
        }

        [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), 
                    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IShellItem
        {
            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, 
            [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs);

            [MethodImpl(MethodImplOptions.InternalCall, 
                        MethodCodeType = MethodCodeType.Runtime)]
            uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, 
                         [In] uint hint, out int piOrder);
        }

        #endregion

        [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern int SHCreateItemFromParsingName
         ([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, 
         ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
    }
}

ISelect.cs

using System.Windows.Forms;

namespace FolderDialog.Bll.FolderDialog
{
    public interface ISelect
    {
        string InitialFolder { get; set; }
        string DefaultFolder { get; set; }
        string Folder { get; set; }
        DialogResult ShowDialog();
        DialogResult ShowDialog(IWin32Window owner);
        DialogResult ShowVistaDialog(IWin32Window owner);
        DialogResult ShowLegacyDialog(IWin32Window owner);
        void Dispose();
    }
}

Console Layer

A .NET Core 3.0 Console Application.

using System;

namespace FolderDialog.Console
{    
    class Program
    {        
        [STAThread]
        static void Main(string[] args)
        {
            System.Console.WriteLine(": : : : : Folder Dialog App : : : : :");
            Bll.FolderDialog.ISelect select = new Bll.FolderDialog.Select();
            select.InitialFolder = "C:\\";
            select.ShowDialog();
            System.Console.WriteLine($"Folder Selected: {select.Folder}");
            System.Console.WriteLine("Press any key to continue...");
            System.Console.ReadLine();
        }
    }
}

Points of Interest

This code uses COM references to interact with Windows dialog boxes. Then, it uses a WindowWrapper class in the Select.cs file to instantiate the ShowDialog IWin32Window value.

The code snippets above will work in .NET Framework and in .NET Core.

Another exciting tidbit of information. This code is a merging of two projects from resources from approximately 10 years ago. The original resources are:

History

  • 6th January, 2019 19:44 EST - Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

ftwnate917
United States United States
No Biography provided

Comments and Discussions

 
QuestionWhat about Linux or MacOS Pin
Wendelius9-Jan-20 18:14
mveWendelius9-Jan-20 18:14 
AnswerRe: What about Linux or MacOS Pin
ftwnate91710-Jan-20 13:43
Memberftwnate91710-Jan-20 13:43 
QuestionUsefull, but... Pin
Jerome Dubois8-Jan-20 4:00
MemberJerome Dubois8-Jan-20 4:00 
AnswerRe: Usefull, but... Pin
Casey Shaar8-Jan-20 10:51
MemberCasey Shaar8-Jan-20 10:51 
QuestionI miss the point Pin
snoopy0018-Jan-20 3:51
Membersnoopy0018-Jan-20 3:51 
QuestionScreenshot Pin
dandy727-Jan-20 6:48
Memberdandy727-Jan-20 6:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 7 Jan 2020

Stats

3.6K views
183 downloads
6 bookmarked