Диалоговое окно открытия файла MVVM
-
20-08-2019 - |
Вопрос
Хорошо, я действительно хотел бы знать, как опытные разработчики MVVM обрабатывают диалоговое окно openfile в WPF.
Я действительно не хочу делать это в моей ViewModel (где на 'Browse' ссылаются через DelegateCommand)
void Browse(object param)
{
//Add code here
OpenFileDialog d = new OpenFileDialog();
if (d.ShowDialog() == true)
{
//Do stuff
}
}
Потому что я считаю, что это противоречит методологии MVVM.
Что мне делать?
Решение
Лучшее, что можно сделать здесь, - это воспользоваться услугой.
Служба - это просто класс, к которому вы обращаетесь из центрального хранилища служб, часто из контейнера IOC. Затем служба реализует то, что вам нужно, например OpenFileDialog.
Итак, если у вас есть IFileDialogService
в контейнере Unity, вы можете сделать ...
void Browse(object param)
{
var fileDialogService = container.Resolve<IFileDialogService>();
string path = fileDialogService.OpenFileDialog();
if (!string.IsNullOrEmpty(path))
{
//Do stuff
}
}
Другие советы
Я бы хотел прокомментировать один из ответов, но, увы, моя репутация недостаточно высока, чтобы сделать это.
Наличие вызова, такого как OpenFileDialog (), нарушает шаблон MVVM, поскольку подразумевает представление (диалог) в модели представления. Модель представления может вызывать что-то вроде GetFileName () (то есть, если простого связывания недостаточно), но не должно заботиться о том, как получить имя файла.
Я использую сервис, который я, например, могу передать в конструктор моей viewModel или разрешить с помощью внедрения зависимостей. например.
public interface IOpenFileService
{
string FileName { get; }
bool OpenFileDialog()
}
и класс, реализующий его, используя OpenFileDialog под капотом. В viewModel я использую только интерфейс и, таким образом, могу смоделировать / заменить его, если это необходимо.
ViewModel не должен открывать диалоги или даже знать об их существовании. Если виртуальная машина размещена в отдельной DLL, в проекте не должно быть ссылки на PresentationFramework.
Мне нравится использовать вспомогательный класс в представлении для общих диалогов.
Вспомогательный класс предоставляет команду (не событие), с которой окно связывается в XAML. Это подразумевает использование RelayCommand в представлении. Вспомогательный класс является DepencyObject, поэтому он может привязываться к модели представления.
class DialogHelper : DependencyObject
{
public ViewModel ViewModel
{
get { return (ViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(DialogHelper),
new UIPropertyMetadata(new PropertyChangedCallback(ViewModelProperty_Changed)));
private static void ViewModelProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (ViewModelProperty != null)
{
Binding myBinding = new Binding("FileName");
myBinding.Source = e.NewValue;
myBinding.Mode = BindingMode.OneWayToSource;
BindingOperations.SetBinding(d, FileNameProperty, myBinding);
}
}
private string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { SetValue(FileNameProperty, value); }
}
private static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName", typeof(string), typeof(DialogHelper),
new UIPropertyMetadata(new PropertyChangedCallback(FileNameProperty_Changed)));
private static void FileNameProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("DialogHelper.FileName = {0}", e.NewValue);
}
public ICommand OpenFile { get; private set; }
public DialogHelper()
{
OpenFile = new RelayCommand(OpenFileAction);
}
private void OpenFileAction(object obj)
{
OpenFileDialog dlg = new OpenFileDialog();
if (dlg.ShowDialog() == true)
{
FileName = dlg.FileName;
}
}
}
Вспомогательному классу нужна ссылка на экземпляр ViewModel. Смотрите словарь ресурсов. Сразу после построения устанавливается свойство ViewModel (в той же строке XAML). Это когда свойство FileName в классе помощника связано со свойством FileName в модели представления.
<Window x:Class="DialogExperiment.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DialogExperiment"
xmlns:vm="clr-namespace:DialogExperimentVM;assembly=DialogExperimentVM"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:ViewModel x:Key="viewModel" />
<local:DialogHelper x:Key="helper" ViewModel="{StaticResource viewModel}"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource viewModel}">
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Open" Command="{Binding Source={StaticResource helper}, Path=OpenFile}" />
</MenuItem>
</Menu>
</DockPanel>
</Window>
Сервис - это как открыть представление из модели представления. У меня есть свойство Dependency, и в chnage этого свойства я открываю FileDialog и читаю путь, обновляю свойство и, следовательно, свойство bound виртуальной машины
Я решил это для себя таким образом:
- В Видовая модель Я определил интерфейс и работаю с ним в ViewModel
- В Вид Я реализовал этот интерфейс.
CommandImpl не реализован в приведенном ниже коде.
Видовая модель:
namespace ViewModels.Interfaces
{
using System.Collections.Generic;
public interface IDialogWindow
{
List<string> ExecuteFileDialog(object owner, string extFilter);
}
}
namespace ViewModels
{
using ViewModels.Interfaces;
public class MyViewModel
{
public ICommand DoSomeThingCmd { get; } = new CommandImpl((dialogType) =>
{
var dlgObj = Activator.CreateInstance(dialogType) as IDialogWindow;
var fileNames = dlgObj?.ExecuteFileDialog(null, "*.txt");
//Do something with fileNames..
});
}
}
Вид:
namespace Views
{
using ViewModels.Interfaces;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
public class OpenFilesDialog : IDialogWindow
{
public List<string> ExecuteFileDialog(object owner, string extFilter)
{
var fd = new OpenFileDialog();
fd.Multiselect = true;
if (!string.IsNullOrWhiteSpace(extFilter))
{
fd.Filter = extFilter;
}
fd.ShowDialog(owner as Window);
return fd.FileNames.ToList();
}
}
}
XAML:
<Window
xmlns:views="clr-namespace:Views"
xmlns:viewModels="clr-namespace:ViewModels"
>
<Window.DataContext>
<viewModels:MyViewModel/>
</Window.DataContext>
<Grid>
<Button Content = "Open files.." Command="{Binding DoSomeThingCmd}" CommandParameter="{x:Type views:OpenFilesDialog}"/>
</Grid>
</Window>