wpf 打开文件对话框 MVVM

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1043918/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:37:48  来源:igfitidea点击:

Open File Dialog MVVM

wpfmvvmopenfiledialog

提问by Jose

Ok I really would like to know how expert MVVM developers handle an openfile dialog in WPF.

好的,我真的很想知道专家 MVVM 开发人员如何处理 WPF 中的打开文件对话框。

I don't really want to do this in my ViewModel(where 'Browse' is referenced via a DelegateCommand)

我真的不想在我的 ViewModel 中执行此操作(其中“浏览”是通过 DelegateCommand 引用的)

void Browse(object param)
{
    //Add code here
    OpenFileDialog d = new OpenFileDialog();

    if (d.ShowDialog() == true)
    {
        //Do stuff
    }
}

Because I believe that goes against MVVM methodology.

因为我认为这违背了 MVVM 方法。

What do I do?

我该怎么办?

采纳答案by Cameron MacFarland

The best thing to do here is use a service.

在这里做的最好的事情是使用服务。

A service is just a class that you access from a central repository of services, often an IOC container. The service then implements what you need like the OpenFileDialog.

服务只是您从服务的中央存储库(通常是 IOC 容器)访问的一个类。然后该服务会实现您需要的内容,例如 OpenFileDialog。

So, assuming you have an IFileDialogServicein a Unity container, you could do...

因此,假设您IFileDialogService在 Unity 容器中有一个,您可以这样做...

void Browse(object param)
{
    var fileDialogService = container.Resolve<IFileDialogService>();

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}

回答by JAB

I would have liked to comment on one of the answers, but alas, my reputation is not high enough to do so.

我本来想对其中一个答案发表评论,但唉,我的声誉不够高,无法这样做。

Having a call such as OpenFileDialog() violates the MVVM pattern because it implies a view (dialog) in the view model. The view model can call something like GetFileName() (that is, if simple binding is not sufficient), but it should not care how the file name is obtained.

调用 OpenFileDialog() 违反了 MVVM 模式,因为它暗示了视图模型中的视图(对话框)。视图模型可以调用 GetFileName() 之类的东西(也就是说,如果简单绑定是不够的),但它不应该关心文件名是如何获得的。

回答by Botz3000

I use a service which i for example can pass into the constructor of my viewModel or resolve via dependency injection. e.g.

我使用了一个服务,例如,我可以将它传递给我的 viewModel 的构造函数或通过依赖注入解析。例如

public interface IOpenFileService
{
    string FileName { get; }
    bool OpenFileDialog()
}

and a class implementing it, using OpenFileDialog under the hood. In the viewModel, i only use the interface and thus can mock/replace it if needed.

和一个实现它的类,在引擎盖下使用 OpenFileDialog。在 viewModel 中,我只使用接口,因此可以在需要时模拟/替换它。

回答by Paul Williams

The ViewModel should not open dialogs or even know of their existence. If the VM is housed in a separate DLL, the project should not have a reference to PresentationFramework.

ViewModel 不应该打开对话框,甚至不应该知道它们的存在。如果 VM 位于单独的 DLL 中,则项目不应引用 PresentationFramework。

I like to use a helper class in the view for common dialogs.

我喜欢在常见对话框的视图中使用辅助类。

The helper class exposes a command (not an event) which the window binds to in XAML. This implies the use of RelayCommand within the view. The helper class is a DepencyObject so it can bind to the view model.

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;
        }
    }
}

The helper class needs a reference to the ViewModel instance. See the resource dictionary. Just after construction, the ViewModel property is set (in the same line of XAML). This is when the FileName property on the helper class is bound to the FileName property on the view model.

辅助类需要对 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>

回答by Rekshino

I have solved it for me this way:

我已经通过这种方式为我解决了:

  • In ViewModelI have defined an interface and work with it in ViewModel
  • In ViewI have implemented this interface.
  • ViewModel 中,我定义了一个接口并在 ViewModel 中使用它
  • View我已经实现了这个接口。

CommandImpl is not implemented in code below.

CommandImpl 没有在下面的代码中实现。

ViewModel:

视图模型:

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..
        });
    }
}

View:

看法:

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:

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>

回答by Jilt

Having a service is like opening up a view from viewmodel. I have a Dependency property in view, and on the chnage of the property, I open up FileDialog and read the path, update the property and consequently the bound property of the VM

拥有一项服务就像从视图模型中打开一个视图。我在视图中有一个 Dependency 属性,在属性的更改上,我打开 FileDialog 并读取路径,更新属性以及 VM 的绑定属性