WPF OpenFileDialog с шаблоном MVVM?
-
06-07-2019 - |
Вопрос
Я только начал изучать шаблон MVVM для WPF.Я врезался в стену: что вы делаете, когда вам нужно показать OpenFileDialog?
Вот пример пользовательского интерфейса, на котором я пытаюсь его использовать:
При нажатии кнопки обзор должен появиться OpenFileDialog.Когда пользователь выбирает файл из OpenFileDialog, в текстовом поле должен отображаться путь к файлу.
Как я могу сделать это с помощью MVVM?
Обновить:Как я могу сделать это с помощью MVVM и сделать его доступным для модульного тестирования?Приведенное ниже решение не работает для модульного тестирования.
Решение
Что я обычно делаю, так это создаю интерфейс для прикладной службы, которая выполняет эту функцию.В моих примерах я предполагаю, что вы используете что-то вроде MVVM Toolkit или что-то подобное (чтобы я мог получить базовую ViewModel и RelayCommand).
Вот пример чрезвычайно простого интерфейса для выполнения базовых операций ввода-вывода, таких как OpenFileDialog и OpenFile.Я показываю их здесь оба, чтобы вы не подумали, что я предлагаю вам создать один интерфейс с одним методом, чтобы обойти эту проблему.
public interface IOService
{
string OpenFileDialog(string defaultPath);
//Other similar untestable IO operations
Stream OpenFile(string path);
}
В вашем приложении вы бы предоставили реализацию этой службы по умолчанию.Вот как бы вы это употребляли.
public MyViewModel : ViewModel
{
private string _selectedPath;
public string SelectedPath
{
get { return _selectedPath; }
set { _selectedPath = value; OnPropertyChanged("SelectedPath"); }
}
private RelayCommand _openCommand;
public RelayCommand OpenCommand
{
//You know the drill.
...
}
private IOService _ioService;
public MyViewModel(IOService ioService)
{
_ioService = ioService;
OpenCommand = new RelayCommand(OpenFile);
}
private void OpenFile()
{
SelectedPath = _ioService.OpenFileDialog(@"c:\Where\My\File\Usually\Is.txt");
if(SelectedPath == null)
{
SelectedPath = string.Empty;
}
}
}
Так что это довольно просто.Теперь перейдем к последней части:проверяемость.Это должно быть очевидно, но я покажу вам, как сделать простой тест для этого.Я использую Moq для заглушки, но вы, конечно, можете использовать все, что пожелаете.
[Test]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
Mock<IOService> ioServiceStub = new Mock<IOService>();
//We use null to indicate invalid path in our implementation
ioServiceStub.Setup(ioServ => ioServ.OpenFileDialog(It.IsAny<string>()))
.Returns(null);
//Setup target and test
MyViewModel target = new MyViewModel(ioServiceStub.Object);
target.OpenCommand.Execute();
Assert.IsEqual(string.Empty, target.SelectedPath);
}
Вероятно, это сработает у вас.
В CodePlex есть библиотека под названием "SystemWrapper" (http://systemwrapper.codeplex.com) это могло бы избавить вас от необходимости выполнять лот о такого рода вещах.Похоже, FileDialog пока не поддерживается, так что вам определенно придется написать интерфейс для этого.
Надеюсь, это поможет.
Редактировать:
Кажется, я помню, что вы предпочитали TypeMock Isolator для своего фальшивого фреймворка.Вот тот же тест с использованием Isolator:
[Test]
[Isolated]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
IOService ioServiceStub = Isolate.Fake.Instance<IOService>();
//Setup stub arrangements
Isolate.WhenCalled(() => ioServiceStub.OpenFileDialog("blah"))
.WasCalledWithAnyArguments()
.WillReturn(null);
//Setup target and test
MyViewModel target = new MyViewModel(ioServiceStub);
target.OpenCommand.Execute();
Assert.IsEqual(string.Empty, target.SelectedPath);
}
Надеюсь, это тоже будет полезно.
Другие советы
WPF Application Framework (WAF) обеспечивает реализацию для Open и SaveFileDialog.
Пример приложения Writer показывает, как их использовать и как можно тестировать код.
Во-первых, я бы порекомендовал вам начать с Инструментарий WPF MVVM . Это дает вам хороший выбор команд для ваших проектов. Одна особенность, которая стала известной с момента появления шаблона MVVM - это RelayCommand (конечно, есть и другие версии, но я просто придерживаюсь наиболее часто используемых). Это реализация интерфейса ICommand, который позволяет вам создать новую команду в вашей ViewModel.
Возвращаясь к вашему вопросу, вот пример того, как может выглядеть ваша ViewModel.
public class OpenFileDialogVM : ViewModelBase
{
public static RelayCommand OpenCommand { get; set; }
private string _selectedPath;
public string SelectedPath
{
get { return _selectedPath; }
set
{
_selectedPath = value;
RaisePropertyChanged("SelectedPath");
}
}
private string _defaultPath;
public OpenFileDialogVM()
{
RegisterCommands();
}
public OpenFileDialogVM(string defaultPath)
{
_defaultPath = defaultPath;
RegisterCommands();
}
private void RegisterCommands()
{
OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
}
private void ExecuteOpenFileDialog()
{
var dialog = new OpenFileDialog { InitialDirectory = _defaultPath };
dialog.ShowDialog();
SelectedPath = dialog.FileName;
}
}
ViewModelBase и RelayCommand оба из Инструментарий MVVM . Вот как может выглядеть XAML.
<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>
и ваш код XAML.CS.
DataContext = new OpenFileDialogVM();
InitializeComponent();
Вот и все.
По мере знакомства с командами вы также можете установить условия, когда вы хотите, чтобы кнопка «Обзор» была отключена и т. д. Я надеюсь, что это указало вам направление, которое вы хотели.
С моей точки зрения, лучшим вариантом является библиотека призмы и запросы InteractionRequests. Действие по открытию диалога остается внутри xaml и запускается из Viewmodel, в то время как Viewmodel не нужно ничего знать о представлении.
Смотрите также
https://plainionist.github.io///Mvvm-Dialogs/а> р>
В качестве примера см .:
На мой взгляд, лучшим решением является создание пользовательского элемента управления.
Пользовательский элемент управления, который я обычно создаю, состоит из:
- Текстовое поле или текстовый блок
- Кнопка с изображением в качестве шаблона
- Свойство String dependency, в которое будет перенесен путь к файлу
Таким образом, файл * .xaml будет выглядеть следующим образом
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Button Grid.Column="1" Click="Button_Click">
<Button.Template>
<ControlTemplate>
<Image Grid.Column="1" Source="../Images/carpeta.png"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
И файл *.cs:
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
typeof(string),
typeof(customFilePicker),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal));
public string Text
{
get
{
return this.GetValue(TextProperty) as String;
}
set
{
this.SetValue(TextProperty, value);
}
}
public FilePicker()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if(openFileDialog.ShowDialog() == true)
{
this.Text = openFileDialog.FileName;
}
}
В конце вы можете привязать его к вашей модели представления:
<controls:customFilePicker Text="{Binding Text}"/>