【问题标题】:Open directory dialog打开目录对话框
【发布时间】:2010-12-27 16:34:08
【问题描述】:

我希望用户选择一个目录,然后我将生成的文件将保存在该目录中。我知道在 WPF 中我应该使用 Win32 中的 OpenFileDialog,但不幸的是,对话框需要选择文件 - 如果我只是单击确定而不选择一个,它会保持打开状态。我可以通过让用户选择一个文件然后剥离路径以找出它属于哪个目录来“破解”该功能,但这充其量是不直观的。有没有人见过这种做法?

【问题讨论】:

    标签: wpf filesystems dialog


    【解决方案1】:

    您可以为此使用内置的FolderBrowserDialog 类。不要介意它在 System.Windows.Forms 命名空间中。

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

    如果您希望窗口在某些 WPF 窗口之上是模态的,请参阅问题 How to use a FolderBrowserDialog from a WPF application。


    编辑:如果您想要一些比普通、丑陋的 Windows 窗体 FolderBrowserDialog 更花哨的东西,有一些替代方法可以让您使用 Vista 对话框:

    • 第三方库,例如Ookii dialogs (.NET 4.5+)

    • Windows API Code Pack-Shell:

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

      请注意,此对话框在 Windows Vista 之前的操作系统上不可用,因此请务必先检查CommonFileDialog.IsPlatformSupported。

    【讨论】:

    • 请注意,这是一个糟糕的对话框。您不能将路径复制并粘贴到其中,并且它不支持收藏夹。总的来说,我会给它一个 0 分(满分 5 分),并建议没有人使用它。除了在 Windows Vista 推出 the much better folder dialog 之前没有其他合理的选择。有good free libraries 在 Vista+ 上显示好的对话框,在 XP 上显示不好的对话框。
    • 不过,为什么 WPF 提供了出色的 OpenFileDialog 但没有 OpenFolderDialog?这不是有点奇怪吗?为什么这里缺少 WPF?有没有计划在 WPF 中为此对话框添加一个类?
    • 别忘了 FolderBrowserDialog 是一次性的。
    • 请注意,要从WindowsAPICodePack 使用CommonOpenFileDialog,您需要Install-Package WindowsAPICodePack-Shell。答案中提供的链接没有列出。
    • “找不到类型或命名空间 CommonOpenFileDialog”。现在是 2017 年,我无法选择 文件夹
    【解决方案2】:

    我创建了一个 UserControl,它的使用方式如下:

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

    xaml 源代码如下所示:

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

    和代码隐藏

    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();
                }
            }
        }
     }
    

    【讨论】:

    • +1,关于如何编写 UserControl 的好例子。第一个问题:为什么需要be.UpdateSource?依赖属性中的更改通知不应该是自动的吗?
    • 您可以在绑定中指定何时触发更新。默认情况下,它位于 LostFocus 上,但您也可以告诉它在 PropertyChanged 上触发更新。
    • 绑定也会在每次击键时更新。如果用户在更新时进行某种验证(例如 Directory.Exist),可能会导致问题。
    【解决方案3】:

    Ookii 文件夹对话框可以在 Nuget 找到。

    PM&gt; Install-Package Ookii.Dialogs.Wpf

    并且,示例代码如下。

    var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
    if (dialog.ShowDialog(this).GetValueOrDefault())
    {
        textBoxFolderPath.Text = dialog.SelectedPath;
    }
    

    更多使用方法:https://github.com/augustoproiete/ookii-dialogs-wpf

    【讨论】:

    • tnx 你的路是最短的
    【解决方案4】:

    Ookii Dialogs 包含一个用于选择文件夹(而不是文件)的对话框:

    https://github.com/ookii-dialogs

    【讨论】:

      【解决方案5】:

      对于那些不想创建自定义对话框但仍然喜欢 100% WPF 方式并且不想使用单独的 DDL、额外的依赖项或过时的 API 的人,我想出了一个非常简单的 hack,使用 Save作为对话框。

      不需要 using 指令,您可以简单地复制粘贴下面的代码!

      它应该仍然非常用户友好,大多数人都不会注意到。

      这个想法来自这样一个事实,即我们可以很容易地更改该对话框的标题、隐藏文件以及处理生成的文件名。

      这肯定是一个大技巧,但也许它可以很好地满足您的使用需求...

      在这个例子中,我有一个文本框对象来包含生成的路径,但如果你愿意,你可以删除相关的行并使用返回值......

      // 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;
      }
      

      这个 hack 的唯一问题是:

      • 确认按钮仍然显示“保存”而不是“选择目录”之类的内容,但在像我这样的情况下,我“保存”了目录选择,所以它仍然有效......
      • 输入字段仍然显示“文件名”而不是“目录名”,但我们可以说目录是一种文件类型...
      • 仍有“另存为类型”下拉菜单,但其值显示为“目录 (*.this.directory)”,用户无法将其更改为其他内容,适用于我...

      大多数人不会注意到这些,尽管我肯定更喜欢使用官方的 WPF 方式,如果 Microsoft 会从他们的驴子中解脱出来,但在他们这样做之前,这是我的临时解决方案。

      【讨论】:

      • 这很酷。很惊讶似乎没有其他人尝试过这个。当然,NuGet 包要好得多,但如果没有 NuGet WindowsAPICodePack,这是一个很好的方法,可以在不添加任何新包/引用的情况下选择文件夹。
      • Ewww。在我看到dialog.FileName = "select"; // Filename will then be "select.this.directory" 的实施方式之前,您一直在使用。对于非技术性的最终用户来说,这有点令人困惑。但除此之外,这是一个有趣的零依赖 hack。
      【解决方案6】:

      如前面的答案所述,FolderBrowserDialog 是用于此的类。有些人对此对话的外观和行为有(合理的)担忧。好消息是它was "modernized" in NET Core 3.0,因此对于那些编写针对该版本或更高版本的 Windows 窗体或 WPF 应用程序的人来说,它现在是一个可行的选择(不过,如果您仍在使用 NET Framework,那就不走运了)。

      在 .NET Core 3.0 中,Windows 窗体用户 [sic] 在 Windows Vista 中引入了基于 COM 的较新控件:

      到reference System.Windows.Forms in a NET Core WPF app,需要编辑工程文件并添加以下行:

      <UseWindowsForms>true</UseWindowsForms>
      

      这可以直接放在现有的&lt;UseWPF&gt; 元素之后。

      那么就是使用对话框的一个例子:

      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 有一个RootFolder 属性,据说“设置浏览开始的根文件夹”,但无论我设置什么都没有任何区别; SelectedPath 似乎是用于此目的的更好属性,但尾随反斜杠是必需的。

      另外,ShowNewFolderButton 属性似乎也被忽略了,无论如何按钮总是显示。

      【讨论】:

      • 我希望我能多次投票!我在其他没有解释如何在 .NET Core 中获取对话框的答案上浪费了很多时间。谢谢
      【解决方案7】:

      对于Directory Dialog获取Directory Path,首先Add引用System.Windows.Forms,然后Resolve,然后把这段代码放在一个按钮点击。

          var dialog = new FolderBrowserDialog();
          dialog.ShowDialog();
          folderpathTB.Text = dialog.SelectedPath;
      

      (folderpathTB 是 TextBox 的名称,我想在其中放置文件夹路径,或者您也可以将其分配给字符串变量,即)

          string folder = dialog.SelectedPath;
      

      如果你想获得文件名/路径,只需在按钮单击上执行此操作

          FileDialog fileDialog = new OpenFileDialog();
          fileDialog.ShowDialog();
          folderpathTB.Text = fileDialog.FileName;
      

      (folderpathTB 是文本框的名称,我想在其中放置文件路径,或者您也可以将其分配给字符串变量)

      注意:对于文件夹对话框,必须将 System.Windows.Forms.dll 添加到项目中,否则将不起作用。

      【讨论】:

      • 感谢您的回答,但上面的@Heinzi 已经解释了这种方法。
      【解决方案8】:

      我在下面的链接中找到了下面的代码......并且它有效 Select folder dialog WPF

      using Microsoft.WindowsAPICodePack.Dialogs;
      
      var dlg = new CommonOpenFileDialog();
      dlg.Title = "My Title";
      dlg.IsFolderPicker = true;
      dlg.InitialDirectory = currentDirectory;
      
      dlg.AddToMostRecentlyUsedList = false;
      dlg.AllowNonFileSystemItems = false;
      dlg.DefaultDirectory = currentDirectory;
      dlg.EnsureFileExists = true;
      dlg.EnsurePathExists = true;
      dlg.EnsureReadOnly = false;
      dlg.EnsureValidNames = true;
      dlg.Multiselect = false;
      dlg.ShowPlacesList = true;
      
      if (dlg.ShowDialog() == CommonFileDialogResult.Ok) 
      {
        var folder = dlg.FileName;
        // Do something with selected folder string
      }
      

      【讨论】:

        【解决方案9】:

        实现您想要的最佳方式是创建您自己的基于 wpf 的控件,或者使用其他人制作的控件
        为什么 ?因为在 wpf 应用程序中使用 winforms 对话框时会有明显的性能影响(出于某种原因)
        我推荐这个项目
        https://opendialog.codeplex.com/
        或 Nuget:

        PM> Install-Package OpenDialog
        

        它对 MVVM 非常友好,并且不包含 winforms 对话框

        【讨论】:

          【解决方案10】:

          我建议,在 nugget 包中添加:

            Install-Package OpenDialog
          

          那么它的使用方法是:

              Gat.Controls.OpenDialogView openDialog = new Gat.Controls.OpenDialogView();
              Gat.Controls.OpenDialogViewModel vm = (Gat.Controls.OpenDialogViewModel)openDialog.DataContext;
              vm.IsDirectoryChooser = true;
              vm.Show();
          
              WPFLabel.Text = vm.SelectedFilePath.ToString();
          

          这是文档: http://opendialog.codeplex.com/documentation

          适用于文件、带有过滤器的文件、文件夹等

          【讨论】:

            【解决方案11】:

            Ookii VistaFolderBrowserDialog 是您想要的。

            如果您只想要来自Ooki Dialogs 的文件夹浏览器而不需要其他任何东西然后download the Source,请挑选文件夹浏览器所需的文件(提示:7 个文件),它在 .NET 4.5.2 中构建良好。我必须添加对System.Drawing 的引用。将原始项目中的引用与您的进行比较。

            您如何确定哪些文件?在不同的 Visual Studio 实例中打开您的应用和 Ookii。将VistaFolderBrowserDialog.cs 添加到您的应用程序并继续添加文件,直到构建错误消失。您可以在 Ookii 项目中找到依赖项 - 按住 Control 并单击您想要返回到其源代码的那个(双关语)。

            如果您懒得这样做,这里是您需要的文件...

            NativeMethods.cs
            SafeHandles.cs
            VistaFolderBrowserDialog.cs
            \ Interop
               COMGuids.cs
               ErrorHelper.cs
               ShellComInterfaces.cs
               ShellWrapperDefinitions.cs
            

            编辑VistaFolderBrowserDialog.cs 中的第 197 行,除非您想包含他们的Resources.Resx

            抛出新的 InvalidOperationException(Properties.Resources.FolderBrowserDialogNoRootFolder);

            throw new InvalidOperationException("Unable to retrieve the root folder.");
            

            按照他们的license.txt将他们的版权声明添加到您的应用中

            \Ookii.Dialogs.Wpf.Sample\MainWindow.xaml.cs 第 160-169 行中的代码是您可以使用的示例,但对于 WPF,您需要从 MessageBox.Show(this, 中删除 this,。

            在我的机器上工作 [TM]

            【讨论】:

              【解决方案12】:

              这些答案都不适合我(通常缺少参考资料或类似的东西)

              但这很简单:

              Using FolderBrowserDialog in WPF application

              添加对System.Windows.Forms 的引用并使用此代码:

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

              无需追踪丢失的包裹。或者添加大量的类

              这给了我一个现代的文件夹选择器,还允许你创建一个新文件夹

              我还没有看到部署到其他机器时的影响

              【讨论】:

                【解决方案13】:

                我知道这是一个老问题,但一个简单的方法是使用 WPF 提供的 FileDialog 选项并使用 System.IO.Path.GetDirectory(filename)。

                【讨论】:

                • 但是用户必须选择一个文件,即使他被告知选择一个文件夹。没有经验的用户此时可能会致电 HelpDesk,询问为什么在必须选择文件夹时还要选择文件
                • 要求任何给定文件夹中至少有一个文件,否则无法选择
                【解决方案14】:

                你可以在 WPF 中像这样使用 smth。我已经创建了示例方法。 检查下面。

                public string getFolderPath()
                {
                           // Create OpenFileDialog 
                           Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
                
                           OpenFileDialog openFileDialog = new OpenFileDialog();
                           openFileDialog.Multiselect = false;
                
                           openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                           if (openFileDialog.ShowDialog() == true)
                           {
                               System.IO.FileInfo fInfo = new System.IO.FileInfo(openFileDialog.FileName);
                               return fInfo.DirectoryName;
                           }
                           return null;           
                       }
                

                【讨论】:

                • 这需要用户从文件夹中选择一个文件。如果文件夹为空,则无法选择文件夹。
                • 是的,我明白,这是某种解决方法,不是解决此问题的完美解决方案。
                【解决方案15】:
                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();
                        }
                    }
                }
                

                【讨论】:

                • 您可以使用 WebBrowser 进行此操作以避免 winforms 依赖
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-04-28
                • 1970-01-01
                相关资源
                最近更新 更多