打开目录对话框

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

我认为这是一个更好的解决方案:https://dev59.com/wLfna4cB1Zd3GeqPnCJR - Heisenberg
我认为这是一个更好的解决方案:https://stackoverflow.com/questions/58569627/is-there-some-way-to-use-a-folder-seletor-folderbrowserdialog-in-wpf-core - undefined
18个回答

457
你可以使用内置的FolderBrowserDialog类来实现这个功能。不用在意它位于System.Windows.Forms命名空间中。
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
    System.Windows.Forms.DialogResult result = dialog.ShowDialog();
}

如果你想让窗口在某个WPF窗口上以模态方式显示,请参考问题如何在WPF应用程序中使用FolderBrowserDialog

编辑:如果你想要比普通且丑陋的Windows Forms FolderBrowserDialog更加精致的东西,那么有一些替代方案可以让你使用Vista对话框:

  • 第三方库,例如Ookii对话框(.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


92
请注意,这是一个糟糕的对话框。你无法将路径复制并粘贴到其中,也不支持收藏夹文件夹。总的来说,我会给它0分(满分5分),并建议谁都不要使用它,除非在Windows Vista发布之前没有合理的替代方案,更好的文件夹对话框已经出现了。有一些好用的免费库可以在Vista及以上系统中显示好用的对话框,在XP系统中则显示糟糕的对话框。 - Roman Starkov
88
为什么WPF提供了很好用的OpenFileDialog,但却没有OpenFolderDialog呢?这不是有点奇怪吗?为什么WPF在这方面缺乏呢?是否有计划在WPF中添加一个类来处理此对话框呢? - Paul-Sebastian Manole
16
别忘了,FolderBrowserDialog是可处理的对象。 - LosManos
10
请注意,要使用WindowsAPICodePack中的CommonOpenFileDialog,您需要安装WindowsAPICodePack-Shell软件包。答案提供的链接没有列出这一点。 - Nikola Novak
7
“类型或命名空间CommonOpenFileDialog无法找到”。这是2017年,我无法选择一个“文件夹”。 - Nick.McDermaid
显示剩余15条评论

49

我创建了一个用户控件,可以像这样使用:

  <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
+1,写UserControl的好例子。有一个问题:为什么需要be.UpdateSource?在依赖属性中,改变通知不应该是自动的吗? - Heinzi
4
你可以在绑定中指定何时触发更新。默认情况下是在失去焦点时触发,但你也可以告诉它在 PropertyChanged 时触发更新。 - Alexandra
3
每次按键后,绑定也会得到更新。如果用户在更新时执行某种验证(例如Directory.Exist),可能会导致问题。请注意,本人只提供翻译,不提供解释或其他额外的内容。 - adrianm

37

如前面的回答所述,FolderBrowserDialog是用于此问题的类。一些人对此对话框的外观和行为有(合理的)担忧。好消息是,在.NET Core 3.0中它“现代化”了,因此现在是一种可行的选择,适用于编写针对该版本或更高版本的Windows Forms或WPF应用程序的人(如果仍在使用NET Framework,则没有希望)。

在.NET Core 3.0中,Windows Forms用户使用了一个新的基于COM的控件,该控件在Windows Vista中首次引入:FolderBrowserDialog in NET Core 3.0

在NET Core WPF应用程序中引用System.Windows.Forms,需要编辑项目文件并添加以下行:

<UseWindowsForms>true</UseWindowsForms>

这可以直接放置在现有的<UseWPF>元素之后。

然后只需要使用对话框:

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属性似乎也被忽略了,该按钮始终显示。


2
我希望我能多次点赞!我浪费了很多时间去尝试其他答案,但它们都没有解释如何在.NET Core中获取对话框。谢谢。 - Avrohom Yisroel
唯一缺少的是,您需要重新加载项目才能使UseWindowsForms生效! - Jonathan Willcock

25

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

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


谢谢,你的方法最短。 - ehsan wwe

19

对于那些不想创建自定义对话框,但仍然希望100%使用WPF方式且不想使用单独的DDL、附加依赖项或过时API的人,我想到了一个非常简单的解决方法,即利用"另存为"对话框。

不需要使用指令,您只需复制下面的代码即可!

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

这个想法来自于我们可以轻松地更改对话框的标题、隐藏文件并解决由此产生的文件名的方法。

这肯定是一个很大的hack,但也许它对你的使用情况可以完美地胜任...

在此示例中,我有一个文本框对象来包含结果路径,但是如果您愿意,您可以删除相关行并使用返回值...

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

这种方法唯一的问题是:

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

大多数人不会注意到这些问题,尽管我肯定更喜欢使用官方的WPF方式,如果微软能够理智些,但在他们采取行动之前,这就是我的临时解决方案。


1
这很酷。惊讶的是似乎没有其他人尝试过这个。当然,NuGet包要好得多,但如果没有NuGet WindowsAPICodePack,这是一种极好的方法来“黑客”选择文件夹的能力,而不需要添加任何新的包/引用。 - Code Novice
呃,我看到 dialog.FileName = "select"; // Filename will then be "select.this.directory" 的实现方式之前,感觉有点混乱,对于非技术终端用户来说可能会有些困惑。但除此之外,这是一个有趣的、零依赖的技巧。 - ruffin
底部的按钮仍然显示为“保存”,这不奇怪吗?这会让用户很容易混淆。简直是不可能完成的任务! - Gsv

15

7

若要获取文件夹路径,请先引用 System.Windows.Forms,然后解析,最后在按钮单击事件中加入以下代码。

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

(folderpathTB是我想要放置文件夹路径的文本框的名称,或者您也可以将其分配给字符串变量)
    string folder = dialog.SelectedPath;

如果您想获取文件名/路径,只需在按钮单击时执行以下操作

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

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

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


谢谢您的回答,但是@Heinzi已经在上面解释了这种方法。 - Alexandra

6

我在以下链接中找到了下面的代码...并且它可以工作:选择文件夹对话框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
}

5
我建议在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 适用于文件、带过滤器的文件、文件夹等。

4

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

PM> Install-Package OpenDialog

它非常支持MVVM模式,并且不会包装WinForms对话框。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接