打开文件对话框MVVM
好吧,我真的很想知道MVVM开发人员是如何处理WPF中的打开文件对话框的。
我真的不想在我的ViewModel(其中“浏览”通过DelegateCommand引用)
void Browse(object param) { //Add code here OpenFileDialog d = new OpenFileDialog(); if (d.ShowDialog() == true) { //Do stuff } }
因为我认为这违背了MVVM方法。
我该怎么办?
这里要做的最好的事情就是使用服务。
服务只是一个从中央服务器存储库(通常是IOC容器)访问的类。 然后服务实现你所需要的OpenFileDialog。
所以,假设你有一个在Unity容器中的IFileDialogService
,你可以做…
void Browse(object param) { var fileDialogService = container.Resolve<IFileDialogService>(); string path = fileDialogService.OpenFileDialog(); if (!string.IsNullOrEmpty(path)) { //Do stuff } }
我想就其中一个答案发表评论,但唉,我的名声还不够高。
有一个像OpenFileDialog()这样的调用违反了MVVM模式,因为它隐含了视图模型中的视图(对话框)。 视图模型可以调用类似GetFileName()(也就是说,如果简单的绑定是不够的),但它不应该在乎如何获得文件名。
我使用一个服务,例如我可以传递到我的viewModel的构造函数或通过dependency injection来解决。 例如
public interface IOpenFileService { string FileName { get; } bool OpenFileDialog() }
和一个实现它的类,使用OpenFileDialog。 在viewModel中,我只使用接口,因此可以模拟/replace它,如果需要的话。
ViewModel不应该打开对话框,甚至不知道它们的存在。 如果虚拟机位于独立的DLL中,则该项目不应包含对PresentationFramework的引用。
我喜欢在视图中使用助手类来进行常见的对话。
helper类暴露了窗口绑定到XAML中的命令(不是事件)。 这意味着在视图内使用RelayCommand。 helper类是一个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实例的引用。 查看资源字典。 在构build之后,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>
有一个服务就像打开viewmodel视图。 我有一个依赖属性的视图,并在属性的chnage,我打开FileDialog并阅读path,更新属性,从而更新VM的绑定属性
我已经为我解决了这个问题:
- 在ViewModel中,我定义了一个接口,并在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>