Open directory dialog

Viewed 388693

I want the user to select a directory where a file that I will then generate will be saved. I know that in WPF I should use the OpenFileDialog from Win32, but unfortunately the dialog requires file(s) to be selected - it stays open if I simply click OK without choosing one. I could "hack up" the functionality by letting the user pick a file and then strip the path to figure out which directory it belongs to but that's unintuitive at best. Has anyone seen this done before?

16 Answers

You can use the built-in FolderBrowserDialog class for this. Don't mind that it's in the System.Windows.Forms namespace.

using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
    System.Windows.Forms.DialogResult result = dialog.ShowDialog();
}

If you want the window to be modal over some WPF window, see the question How to use a FolderBrowserDialog from a WPF application.


EDIT: If you want something a bit more fancy than the plain, ugly Windows Forms FolderBrowserDialog, there are some alternatives that allow you to use the Vista dialog instead:

  • Third-party libraries, such as Ookii dialogs (.NET 4.5+)

  • The Windows API Code Pack-Shell:

      using Microsoft.WindowsAPICodePack.Dialogs;
    
      ...
    
      var dialog = new CommonOpenFileDialog();
      dialog.IsFolderPicker = true;
      CommonFileDialogResult result = dialog.ShowDialog();
    

    Note that this dialog is not available on operating systems older than Windows Vista, so be sure to check CommonFileDialog.IsPlatformSupported first.

I created a UserControl which is used like this:

  <UtilitiesWPF:FolderEntry Text="{Binding Path=LogFolder}" Description="Folder for log files"/>

The xaml source looks like this:

<UserControl x:Class="Utilities.WPF.FolderEntry"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DockPanel>
        <Button Margin="0" Padding="0" DockPanel.Dock="Right" Width="Auto" Click="BrowseFolder">...</Button>
        <TextBox Height="Auto" HorizontalAlignment="Stretch" DockPanel.Dock="Right" 
           Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
    </DockPanel>
</UserControl>

and the code-behind

public partial class FolderEntry {
    public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FolderEntry), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(FolderEntry), new PropertyMetadata(null));

    public string Text { get { return GetValue(TextProperty) as string; } set { SetValue(TextProperty, value); }}

    public string Description { get { return GetValue(DescriptionProperty) as string; } set { SetValue(DescriptionProperty, value); } }

    public FolderEntry() { InitializeComponent(); }

    private void BrowseFolder(object sender, RoutedEventArgs e) {
        using (FolderBrowserDialog dlg = new FolderBrowserDialog()) {
            dlg.Description = Description;
            dlg.SelectedPath = Text;
            dlg.ShowNewFolderButton = true;
            DialogResult result = dlg.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK) {
                Text = dlg.SelectedPath;
                BindingExpression be = GetBindingExpression(TextProperty);
                if (be != null)
                    be.UpdateSource();
            }
        }
    }
 }

As stated in earlier answers, FolderBrowserDialog is the class to use for this. Some people have (justifiable) concerns with the appearance and behaviour of this dialog. The good news is that it was "modernized" in NET Core 3.0, so is now a viable option for those writing either Windows Forms or WPF apps targeting that version or later (you're out of luck if still using NET Framework though).

In .NET Core 3.0, Windows Forms users [sic] a newer COM-based control that was introduced in Windows Vista: FolderBrowserDialog in NET Core 3.0

To reference System.Windows.Forms in a NET Core WPF app, it is necessary to edit the project file and add the following line:

<UseWindowsForms>true</UseWindowsForms>

This can be placed directly after the existing <UseWPF> element.

Then it's just a case of using the dialog:

using System;
using System.Windows.Forms;

...

using var dialog = new FolderBrowserDialog
{
    Description = "Time to select a folder",
    UseDescriptionForTitle = true,
    SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
        + Path.DirectorySeparatorChar,
    ShowNewFolderButton = true
};

if (dialog.ShowDialog() == DialogResult.OK)
{
    ...
}

FolderBrowserDialog has a RootFolder property that supposedly "sets the root folder where the browsing starts from" but whatever I set this to it didn't make any difference; SelectedPath seemed to be the better property to use for this purpose, however the trailing backslash is required.

Also, the ShowNewFolderButton property seems to be ignored as well, the button is always shown regardless.

For those who don't want to create a custom dialog but still prefer a 100% WPF way and don't want to use separate DDLs, additional dependencies or outdated APIs, I came up with a very simple hack using the Save As dialog.

No using directive needed, you may simply copy-paste the code below !

It should still be very user-friendly and most people will never notice.

The idea comes from the fact that we can change the title of that dialog, hide files, and work around the resulting filename quite easily.

It is a big hack for sure, but maybe it will do the job just fine for your usage...

In this example I have a textbox object to contain the resulting path, but you may remove the related lines and use a return value if you wish...

// Create a "Save As" dialog for selecting a directory (HACK)
var dialog = new Microsoft.Win32.SaveFileDialog();
dialog.InitialDirectory = textbox.Text; // Use current value for initial dir
dialog.Title = "Select a Directory"; // instead of default "Save As"
dialog.Filter = "Directory|*.this.directory"; // Prevents displaying files
dialog.FileName = "select"; // Filename will then be "select.this.directory"
if (dialog.ShowDialog() == true) {
    string path = dialog.FileName;
    // Remove fake filename from resulting path
    path = path.Replace("\\select.this.directory", "");
    path = path.Replace(".this.directory", "");
    // If user has changed the filename, create the new directory
    if (!System.IO.Directory.Exists(path)) {
        System.IO.Directory.CreateDirectory(path);
    }
    // Our final value is in path
    textbox.Text = path;
}

The only issues with this hack are :

  • Acknowledge button still says "Save" instead of something like "Select directory", but in a case like mines I "Save" the directory selection so it still works...
  • Input field still says "File name" instead of "Directory name", but we can say that a directory is a type of file...
  • There is still a "Save as type" dropdown, but its value says "Directory (*.this.directory)", and the user cannot change it for something else, works for me...

Most people won't notice these, although I would definitely prefer using an official WPF way if microsoft would get their heads out of their asses, but until they do, that's my temporary fix.

It seems that the Microsoft.Win32 .NET library does not support selecting folders (only files), so you are out of luck in WPF (as of 7/2022). I feel the best option now is Ookii for WPF: https://github.com/ookii-dialogs/ookii-dialogs-wpf. It works great and as expected in WPF minus Microsoft support. You can get it as a NuGet package. Code behind XAML View:

public partial class ExportRegionView : UserControl
{
    public ExportRegionView()
    {
        InitializeComponent();
    }
    private void SavePath(object sender, RoutedEventArgs e)
    {
        var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
        dialog.Description = "SIPAS Export Folder";
        dialog.UseDescriptionForTitle = true;
        if (dialog.ShowDialog().GetValueOrDefault())
        {
            ExportPath.Text = dialog.SelectedPath;
        }
    }
}

XAML: <Button Grid.Row="1" Grid.Column="3" Style="{DynamicResource Esri_Button}" Click="SavePath" Margin="5,5,5,5">Path</Button>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Gearplay
{
    /// <summary>
    /// Логика взаимодействия для OpenFolderBrows.xaml
    /// </summary>
    public partial class OpenFolderBrows : Page
    {
        internal string SelectedFolderPath { get; set; }
        public OpenFolderBrows()
        {
            InitializeComponent();
            Selectedpath();
            InputLogicalPathCollection();
             
        }

        internal void Selectedpath()
        {
            Browser.Navigate(@"C:\");
            
            Browser.Navigated += Browser_Navigated;
        }

        private void Browser_Navigated(object sender, NavigationEventArgs e)
        {
            SelectedFolderPath = e.Uri.AbsolutePath.ToString();
            //MessageBox.Show(SelectedFolderPath);
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
          
           
        }
        
        string [] testing { get; set; }
        private void InputLogicalPathCollection()
        {            // add Menu items for Cotrol 
            string[] DirectoryCollection_Path = Environment.GetLogicalDrives(); // Get Local Drives
            testing = new string[DirectoryCollection_Path.Length];
            //MessageBox.Show(DirectoryCollection_Path[0].ToString());
            MenuItem[]  menuItems = new MenuItem[DirectoryCollection_Path.Length]; // Create Empty Collection
            for(int i=0;i<menuItems.Length;i++)
            {
                // Create collection depend how much logical drives 
                menuItems[i] = new MenuItem();
                menuItems[i].Header = DirectoryCollection_Path[i];
                menuItems[i].Name = DirectoryCollection_Path[i].Substring(0,DirectoryCollection_Path.Length-1);
                DirectoryCollection.Items.Add(menuItems[i]);
                menuItems[i].Click += OpenFolderBrows_Click;
                testing[i]= DirectoryCollection_Path[i].Substring(0, DirectoryCollection_Path.Length - 1);
            }

            

        }
        
        private void OpenFolderBrows_Click(object sender, RoutedEventArgs e)
        {

            foreach (string str in testing)
            {
                if (e.OriginalSource.ToString().Contains("Header:"+str)) // Navigate to Local drive
                {
                    Browser.Navigate(str + @":\");
                   
                }


            }


        }

        private void Goback_Click(object sender, RoutedEventArgs e)
        {// Go Back
            try
            {
                Browser.GoBack();
            }catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void Goforward_Click(object sender, RoutedEventArgs e)
        { //Go Forward
            try
            {
                Browser.GoForward();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        private void FolderForSave_Click(object sender, RoutedEventArgs e)
        {
            // Separate Click For Go Back same As Close App With send string var to Main Window ( Main class etc.) 
            this.NavigationService.GoBack();
        }
    }
}
Related