【发布时间】:2010-12-27 16:34:08
【问题描述】:
我希望用户选择一个目录,然后我将生成的文件将保存在该目录中。我知道在 WPF 中我应该使用 Win32 中的 OpenFileDialog,但不幸的是,对话框需要选择文件 - 如果我只是单击确定而不选择一个,它会保持打开状态。我可以通过让用户选择一个文件然后剥离路径以找出它属于哪个目录来“破解”该功能,但这充其量是不直观的。有没有人见过这种做法?
【问题讨论】:
标签: wpf filesystems dialog
我希望用户选择一个目录,然后我将生成的文件将保存在该目录中。我知道在 WPF 中我应该使用 Win32 中的 OpenFileDialog,但不幸的是,对话框需要选择文件 - 如果我只是单击确定而不选择一个,它会保持打开状态。我可以通过让用户选择一个文件然后剥离路径以找出它属于哪个目录来“破解”该功能,但这充其量是不直观的。有没有人见过这种做法?
【问题讨论】:
标签: wpf filesystems dialog
您可以为此使用内置的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+)
using Microsoft.WindowsAPICodePack.Dialogs;
...
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
请注意,此对话框在 Windows Vista 之前的操作系统上不可用,因此请务必先检查CommonFileDialog.IsPlatformSupported。
【讨论】:
WindowsAPICodePack 使用CommonOpenFileDialog,您需要Install-Package WindowsAPICodePack-Shell。答案中提供的链接没有列出。
我创建了一个 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();
}
}
}
}
【讨论】:
be.UpdateSource?依赖属性中的更改通知不应该是自动的吗?
Ookii 文件夹对话框可以在 Nuget 找到。
PM> Install-Package Ookii.Dialogs.Wpf
并且,示例代码如下。
var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
if (dialog.ShowDialog(this).GetValueOrDefault())
{
textBoxFolderPath.Text = dialog.SelectedPath;
}
【讨论】:
【讨论】:
对于那些不想创建自定义对话框但仍然喜欢 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 的唯一问题是:
大多数人不会注意到这些,尽管我肯定更喜欢使用官方的 WPF 方式,如果 Microsoft 会从他们的驴子中解脱出来,但在他们这样做之前,这是我的临时解决方案。
【讨论】:
dialog.FileName = "select"; // Filename will then be "select.this.directory" 的实施方式之前,您一直在使用。对于非技术性的最终用户来说,这有点令人困惑。但除此之外,这是一个有趣的零依赖 hack。
如前面的答案所述,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>
这可以直接放在现有的<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 属性似乎也被忽略了,无论如何按钮总是显示。
【讨论】:
对于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 添加到项目中,否则将不起作用。
【讨论】:
我在下面的链接中找到了下面的代码......并且它有效 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
}
【讨论】:
实现您想要的最佳方式是创建您自己的基于 wpf 的控件,或者使用其他人制作的控件
为什么 ?因为在 wpf 应用程序中使用 winforms 对话框时会有明显的性能影响(出于某种原因)
我推荐这个项目
https://opendialog.codeplex.com/
或 Nuget:
PM> Install-Package OpenDialog
它对 MVVM 非常友好,并且不包含 winforms 对话框
【讨论】:
我建议,在 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
适用于文件、带有过滤器的文件、文件夹等
【讨论】:
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]
【讨论】:
这些答案都不适合我(通常缺少参考资料或类似的东西)
但这很简单:
Using FolderBrowserDialog in WPF application
添加对System.Windows.Forms 的引用并使用此代码:
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
无需追踪丢失的包裹。或者添加大量的类
这给了我一个现代的文件夹选择器,还允许你创建一个新文件夹
我还没有看到部署到其他机器时的影响
【讨论】:
我知道这是一个老问题,但一个简单的方法是使用 WPF 提供的 FileDialog 选项并使用 System.IO.Path.GetDirectory(filename)。
【讨论】:
你可以在 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;
}
【讨论】:
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();
}
}
}
【讨论】: