Диалог выбора папки WPF
Я разрабатываю приложение WPF4, и в моем приложении мне нужно позволить пользователю выбрать папку, в которой приложение будет что-то хранить (файлы, сгенерированные отчеты и т. Д.).
Мои требования:
Возможность просмотра стандартного дерева папок
Возможность выбрать папку
Внешний вид WPF, этот диалог должен выглядеть как часть современного приложения, разработанного для Windows Vista/7, а не Windows 2000 или даже Win9x.
Как я понимаю, до 2010 года (.Net 4.0) стандартного диалогового окна папок не будет, но, возможно, в версии 4.0 будут некоторые изменения?
Или все, что нужно сделать, это использовать диалог старой школы WinForms? Если это единственный способ сделать то, что мне нужно, как я могу приблизить его к стилю Vista / 7, а не к Win9x?
На некоторых форумах я видел реализацию таких диалогов, но со старыми уродливыми иконками в стиле Windows 95. Это действительно не выглядит красиво.
12 ответов
Я написал об этом в своем блоге давным-давно, поддержка WPF общих файловых диалогов действительно плохая (или, по крайней мере, была в 3.5, я не проверял в версии 4) - но это легко обойти.
Вам нужно добавить правильный манифест в ваше приложение - это даст вам окна сообщений в современном стиле и браузер папок (WinForms FolderBrowserDialog), но не диалоги открытия / сохранения файла WPF, это описано в этих 3 постах (если вам все равно по поводу объяснения и хочу только решение перейти сразу к 3-му):
- Почему я получаю диалоги файлов и окна сообщений в старом стиле с WPF
- Решит ли установка манифеста проблемы со стилем моего окна сообщения WPF?
- Манифест приложения, необходимый для файловых диалогов и окон сообщений в стиле XP и Vista с WPF
К счастью, диалоги открытия / сохранения представляют собой очень тонкие обертки вокруг Win32 API, которые легко вызвать с правильными флагами, чтобы получить стиль Vista/7 (после установки манифеста)
Поваренная книга Windows Presentation Foundation 4.5 от Павла Йосифовича на странице 155 в разделе "Использование общих диалоговых окон" гласит:
"Как насчет выбора папок (вместо файлов)? WPF OpenFileDialog не поддерживает это. Одним из решений является использование класса FolderBrowseDialog в Windows Forms. Другое хорошее решение - использование описанного вкратце Windows API Code Pack".
Я загрузил кодовый пакет API из Windows® API Code Pack для Microsoft®.NET Framework Windows 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
}
Добавьте Windows API Code Pack-Shell в свой проект
using Microsoft.WindowsAPICodePack.Dialogs;
...
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
Если вы не хотите использовать Windows Forms или редактировать файлы манифеста, я пришел к очень простому хаку, используя диалог SaveAs в WPF для фактического выбора каталога.
Нет необходимости в использовании директивы, вы можете просто скопировать код ниже!
Это все еще должно быть очень удобным, и большинство людей никогда не заметят.
Идея исходит из того факта, что мы можем легко изменить заголовок этого диалога, скрыть файлы и обойти полученное имя файла.
Это большой взлом, но, возможно, он отлично подойдет для вашего использования...
В этом примере у меня есть объект текстового поля, в котором содержится результирующий путь, но вы можете удалить связанные строки и использовать возвращаемое значение, если хотите...
// 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, если бы Майкрософт вытащил свои головы из задниц, но пока они не заметят, это мое временное исправление.
Класс from — это рекомендуемый способ отображения диалогового окна, позволяющего пользователю выбрать папку.
До недавнего времени внешний вид и поведение этого диалогового окна не соответствовали другим диалоговым окнам файловой системы, что является одной из причин, по которой люди неохотно использовали его.
Хорошей новостью является то, что он был «модернизирован» в NET Core 3.0, поэтому теперь это приемлемый вариант для тех, кто пишет приложения Windows Forms или WPF, ориентированные на эту или более позднюю версию.
В .NET Core 3.0 пользователи Windows Forms [sic] используют новый элемент управления на основе COM, который был представлен в Windows Vista:
Для ссылки
System.Windows.Forms
в приложении NET Core WPF необходимо отредактировать файл проекта и добавить следующую строку:
<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
свойство, похоже, также игнорируется, кнопка всегда отображается независимо.
MVVM + WinForms FolderBrowserDialog как поведение
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
Microsoft.Win32.OpenFileDialog - это стандартный диалог, который используется любым приложением в Windows. Ваш пользователь не будет удивлен его внешним видом при использовании WPF в.NET 4.0
Диалог был изменен в Vista. WPF в.NET 3.0 и 3.5 все еще использовал устаревшее диалоговое окно, но это было исправлено в.NET 4.0. Я могу только догадываться, что вы начали эту тему, потому что вы видите этот старый диалог. Что, вероятно, означает, что вы на самом деле запускаете программу, нацеленную на 3.5. Да, оболочка Winforms обновилась и показывает версию Vista. Класс System.Windows.Forms.OpenFileDialog, вам необходимо добавить ссылку на System.Windows.Forms.
Основываясь на ответе Оюна, лучше использовать свойство зависимости для 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);
}
}
}
Диалоги Ookii для WPF имеют VistaFolderBrowserDialog
Класс, обеспечивающий полную реализацию диалогового окна браузера папок для WPF.
Также есть версия, которая работает с Windows Forms.
Только такой диалог - FileDialog. Это часть WinForms, но на самом деле это единственная оболочка вокруг стандартного диалогового окна WinAPI. И я не думаю, что это некрасиво, это на самом деле часть ОС, так что похоже на ОС, на которой он запущен.
Иначе ничем не поможет. Вам либо нужно искать стороннюю реализацию, либо бесплатную (и я не думаю, что есть что-то хорошее), либо платную.
Комментарий к исходному вопросу от C. Augusto Proiete предложил диалоги Ookii (https://github.com/ookii-dialogs/ookii-dialogs-wpf). Это то, что я использовал в своей ситуации. Вот как я использовал это в своем приложении.
var dialog = new VistaFolderBrowserDialog()
{
Description = "Select Folder",
RootFolder = Environment.SpecialFolder.Desktop,
ShowNewFolderButton = true,
UseDescriptionForTitle = true
};
var result = dialog.ShowDialog();
if (result.HasValue && result.Value)
{
_mySelectedFolder = dialog.SelectedPath;
}
Просто чтобы сказать одно,
WindowsAPICodePack
не могу открыть
CommonOpenFileDialog
в Windows 7 6.1.7600.