WPF选择文件夹对话框

76

我开发了一个 WPF4 应用程序,在我的应用程序中,我需要让用户选择一个文件夹,该文件夹将用于存储某些东西(文件、生成的报告等)。

我的要求:

  • 能够查看标准文件夹树

  • 能够选择文件夹

  • WPF 外观和感觉,该对话框必须看起来像是为 Windows Vista/7 设计的现代应用程序的一部分,而不是 Windows 2000 或甚至 Win9x。

据我所知,直到2010年(.Net 4.0),都没有标准的文件夹对话框,但也许在4.0版本中有一些变化?

或者我唯一能做的就是使用老式的 WinForms 对话框?如果这是我需要做的唯一方法,那么我如何使它看起来更接近 Vista/7 风格,而不是 Win9x?


2
快来看看Sven Groot的令人惊叹的Ookii.Dialogs,适用于WinForms和WPF,为您提供现代化的“Vista”风格的文件夹和文件对话框。 - lightw8
我正在使用wxPython Python模块 https://github.com/wxWidgets/Phoenix - JinSnow
2
这是一个链接,指向针对.NET 4.5的更新版WPF Ookii对话框,并且可在NuGet上获取 - C. Augusto Proiete
12个回答

117

Windows Presentation Foundation 4.5 Cookbook,作者 Pavel Yosifovich,在“使用通用对话框”一节的第155页中写道:

“那么选择文件夹(而不是文件)怎么办?WPF OpenFileDialog 不支持这种操作。一个解决方案是使用 Windows Forms 的 FolderBrowserDialog 类,另一个好的解决方案是使用不久前描述过的 Windows API Code Pack。”

我从 Windows® API Code Pack for Microsoft® .NET Framework Windows API Code Pack: Where is it? 下载了 API Code Pack,并将 Microsoft.WindowsAPICodePack.dll 和 Microsoft.WindowsAPICodePack.Shell.dll 添加到我的 WPF 4.5 项目的引用中。

示例:

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
}

151
我无法相信微软默认没有在 WPF 中包含 FolderBrowserDialog(文件夹浏览对话框)... - Snooze
25
Windows API Code Packs可以通过Nuget获取,链接在这里:http://www.nuget.org/packages/Windows7APICodePack-Shell/ 和 https://www.nuget.org/packages/Windows7APICodePack-Core/,我用这个方法很顺利。 - Wallace Kelly
3
对我来说它运行良好。我只是写下以下命令,以便帖子包含所有信息。 通过VS中的包管理器控制台安装“Install-Package Windows7APICodePack-Core”,“Install-Package Windows7APICodePack-Shell” - Peter T.
我相信这个文件的链接已经失效了。 - alex
CommonOpenFileDialog是IDisposable的。你应该将代码放在using指令中。 - Alexander Høst
显示剩余3条评论

21

我很久以前在博客上写过这个问题,WPF对于常见文件对话框的支持非常糟糕(至少在3.5版本中是这样的,我没有检查过第4版),但是很容易解决。

您需要向应用程序添加正确的清单,这将为您提供现代样式的消息框和文件夹浏览器(WinFormsFolderBrowserDialog),但不包括WPF文件打开/保存对话框,这些在以下三篇文章中有所描述(如果您只关心解决方案而不想了解说明,请直接跳到第三篇):

幸运的是,打开/保存对话框只是对于Win32 API的非常薄的封装,只需要正确调用一些标志即可获得Vista/7风格(在设置了清单之后)。


18

Windows API Code Pack-Shell添加到您的项目中

using Microsoft.WindowsAPICodePack.Dialogs;

...

var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();

13

如果您不想使用Windows Forms或编辑清单文件,并且希望使用WPF的SaveAs对话框来选择目录,我想出了一个非常简单的方法。

不需要使用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;
}

这个方法的唯一问题是:

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

大多数人注意不到这些问题,虽然我肯定更喜欢使用官方的WPF方法,如果微软能够把他们的头从屁股里拿出来,但在他们这样做之前,这是我的临时解决方案。


1
被低估的答案 - Ahmed Mohammed

9

FolderBrowserDialog类来自System.Windows.Forms,是显示允许用户选择文件夹的对话框的推荐方法。

直到最近,此对话框的外观和行为与其他文件系统对话框不一致,这也是人们不愿使用它的原因之一。

好消息是,FolderBrowserDialog在.NET Core 3.0中进行了“现代化”,因此现在对于那些针对该版本或更高版本编写Windows Forms或WPF应用程序的人来说,这是一个可行的选项。

在.NET Core 3.0中,Windows Forms用户可以使用引入Windows Vista的更新COM控件: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属性似乎也被忽略了,按钮总是显示出来。


5

MVVM + WinForms文件夹浏览器对话框作为行为

public class FolderDialogBehavior : Behavior<Button>
{
    public string SetterName { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Click += OnClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Click -= OnClick;
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        var dialog = new FolderBrowserDialog();
        var result = dialog.ShowDialog();
        if (result == DialogResult.OK && AssociatedObject.DataContext != null)
        {
            var propertyInfo = AssociatedObject.DataContext.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.CanRead && p.CanWrite)
            .Where(p => p.Name.Equals(SetterName))
            .First();

            propertyInfo.SetValue(AssociatedObject.DataContext, dialog.SelectedPath, null);
        }
    }
}

使用方法

     <Button Grid.Column="3" Content="...">
            <Interactivity:Interaction.Behaviors>
                <Behavior:FolderDialogBehavior SetterName="SomeFolderPathPropertyName"/>
            </Interactivity:Interaction.Behaviors>
     </Button>

博客帖子: http://kostylizm.blogspot.ru/2014/03/wpf-mvvm-and-winforms-folder-dialog-how.html

这篇博客讨论了在WPF MVVM应用程序中如何使用WinForms文件夹对话框。它介绍了如何将WinForms文件夹对话框与MVVM设计模式结合使用,并提供了一个示例应用程序来说明如何实现此目标。

5

Microsoft.Win32.OpenFileDialog是Windows上任何应用程序使用的标准对话框。当您在.NET 4.0中使用WPF时,用户不会对其外观感到惊讶。

该对话框在Vista中进行了修改。WPF在.NET 3.0和3.5中仍使用旧版对话框,但在.NET 4.0中已修复。我猜测您启动此线程是因为您看到了旧版对话框。这可能意味着您实际上正在运行针对3.5的程序。是的,Winforms包装器确实升级并显示了Vista版本。如果要使用System.Windows.Forms.OpenFileDialog类,则需要添加对System.Windows.Forms的引用。


18
我认为重点在于 OpenFileDialog 无法用于选择文件夹。 - Neutrino

3

根据Oyun的回答,最好使用依赖属性来处理FolderName。这允许(例如)绑定到子属性,这在原始版本中不起作用。此外,在我调整后的版本中,对话框会选择初始文件夹。

XAML中的用法:

<Button Content="...">
   <i:Interaction.Behaviors>
      <Behavior:FolderDialogBehavior FolderName="{Binding FolderPathPropertyName, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
</Button>

代码:

using System.Windows;
using System.Windows.Forms;
using System.Windows.Interactivity;
using Button = System.Windows.Controls.Button;

public class FolderDialogBehavior : Behavior<Button>
{
    #region Attached Behavior wiring
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Click += OnClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Click -= OnClick;
        base.OnDetaching();
    }
    #endregion

    #region FolderName Dependency Property
    public static readonly DependencyProperty FolderName =
            DependencyProperty.RegisterAttached("FolderName",
            typeof(string), typeof(FolderDialogBehavior));

    public static string GetFolderName(DependencyObject obj)
    {
        return (string)obj.GetValue(FolderName);
    }

    public static void SetFolderName(DependencyObject obj, string value)
    {
        obj.SetValue(FolderName, value);
    }
    #endregion

    private void OnClick(object sender, RoutedEventArgs e)
    {
        var dialog = new FolderBrowserDialog();
        var currentPath = GetValue(FolderName) as string;
        dialog.SelectedPath = currentPath;
        var result = dialog.ShowDialog();
        if (result == DialogResult.OK)
        {
            SetValue(FolderName, dialog.SelectedPath);
        }
    }
}

将这个与@TPowers的答案结合起来,这个很好用。另外,针对"i"命名空间.. xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" - bdeem

3

这是唯一一个不完全是临时应急方案的答案。你写WPF的原因是为了摆脱Windows Forms。使用一个Windows Forms对话框会带来许多你不需要的东西。 - AQuirky

1

只有这样的对话框FileDialog。它是WinForms的一部分,但实际上只是WinAPI标准操作系统文件对话框的包装器。我认为它并不丑陋,实际上它是操作系统的一部分,所以它看起来像运行它的操作系统。

另一方面,没有其他帮助可提供。你要么需要寻找第三方实现,免费的(我认为没有好的),要么付费。


谢谢!这绝对是最好的答案。无论是MVVM还是标准Windows,都非常容易编码。 - Mark Bonafe
4
但这并不允许您选择文件夹... 这个问题的重点在于此。 - Nick.McDermaid

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