C# 使用 MVVM 模式的 WPF OpenFileDialog?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1619505/
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-08-06 19:26:47  来源:igfitidea点击:

WPF OpenFileDialog with the MVVM pattern?

c#wpfxamlmvvmopenfiledialog

提问by Judah Gabriel Himango

I just started learning the MVVM pattern for WPF. I hit a wall: what do you do when you need to show an OpenFileDialog?

我刚开始学习 WPF 的 MVVM 模式。我碰壁了:当你需要展示一个时你会怎么做OpenFileDialog

Here's an example UI I'm trying to use it on:

这是我尝试在其上使用的示例 UI:

alt text

替代文字

When the browse button is clicked, an OpenFileDialogshould be shown. When the user selects a file from the OpenFileDialog, the file path should be displayed in the textbox.

单击浏览按钮时,OpenFileDialog应显示 。当用户从 中选择文件时OpenFileDialog,文件路径应显示在文本框中。

How can I do this with MVVM?

我怎样才能用 MVVM 做到这一点?

Update: How can I do this with MVVM and make it unit test-able? The solution below doesn't work for unit testing.

更新:如何使用 MVVM 执行此操作并使其可进行单元测试?下面的解决方案不适用于单元测试。

采纳答案by Anderson Imes

What I generally do is create an interface for an application service that performs this function. In my examples I'll assume you are using something like the MVVM Toolkit or similar thing (so I can get a base ViewModel and a RelayCommand).

我通常做的是为执行此功能的应用程序服务创建一个接口。在我的示例中,我假设您使用的是 MVVM Toolkit 或类似的东西(这样我就可以得到一个基本的 ViewModel 和一个RelayCommand)。

Here's an example of an extremely simple interface for doing basic IO operations like OpenFileDialogand OpenFile. I'm showing them both here so you don't think I'm suggesting you create one interface with one method to get around this problem.

这是一个用于执行基本 IO 操作(如OpenFileDialog和 )的极其简单的接口示例OpenFile。我在这里展示了它们,所以您不会认为我建议您使用一种方法创建一个界面来解决这个问题。

public interface IOService
{
     string OpenFileDialog(string defaultPath);

     //Other similar untestable IO operations
     Stream OpenFile(string path);
}

In your application, you would provide a default implementation of this service. Here is how you would consume it.

在您的应用程序中,您将提供此服务的默认实现。这是你将如何消费它。

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

So that's pretty simple. Now for the last part: testability. This one should be obvious, but I'll show you how to make a simple test for this. I use Moq for stubbing, but you can use whatever you'd like of course.

所以这很简单。现在是最后一部分:可测试性。这应该是显而易见的,但我将向您展示如何为此进行简单的测试。我使用 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);
}

This will probably work for you.

这可能对你有用。

There is a library out on CodePlex called "SystemWrapper" (http://systemwrapper.codeplex.com) that might save you from having to do a lotof this kind of thing. It looks like FileDialogis not supported yet, so you'll definitely have to write an interface for that one.

CodePlex 上有一个名为“SystemWrapper”(http://systemwrapper.codeplex.com)的库,它可以使您不必做很多此类事情。看起来FileDialog尚不支持,因此您肯定必须为该接口编写一个接口。

Hope this helps.

希望这可以帮助。

Edit:

编辑

I seem to remember you favoring TypeMock Isolator for your faking framework. Here's the same test using Isolator:

我似乎记得你喜欢 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);
}

Hope this is helpful as well.

希望这也有帮助。

回答by Tri Q Tran

Firstly I would recommend you to start off with a WPF MVVM toolkit. This gives you a nice selection of Commands to use for your projects. One particular feature that has been made famous since the MVVM pattern's introduction is the RelayCommand (there are manny other versions of course, but I just stick to the most commonly used). Its an implementation of the ICommand interface that allows you to crate a new command in your ViewModel.

首先,我建议您从WPF MVVM 工具包开始。这为您提供了用于项目的不错的命令选择。自从引入 MVVM 模式以来,一个著名的特性是 RelayCommand(当然还有许多其他版本,但我只坚持最常用的)。它是 ICommand 接口的实现,允许您在 ViewModel 中创建新命令。

Back to your question,here is an example of what your ViewModel may look like.

回到你的问题,这是你的 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;
    }
}

ViewModelBaseand RelayCommandare both from the MVVM Toolkit. Here is what the XAML may look like.

ViewModelBaseRelayCommand都来自MVVM Toolkit。下面是 XAML 的样子。

<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>

and your XAML.CS code behind.

和你背后的 XAML.CS 代码。

DataContext = new OpenFileDialogVM();
InitializeComponent();

Thats it.

就是这样。

As you get more familiar with the commands, you can also set conditions as to when you want the Browse button to be disabled, etc. I hope that pointed you in the direction you wanted.

随着您对这些命令越来越熟悉,您还可以设置条件以决定何时禁用“浏览”按钮等。我希望这为您指明了您想要的方向。

回答by jbe

The WPF Application Framework (WAF)provides an implementation for the Open and SaveFileDialog.

WPF应用程序框架(WAF)提供了开放式和SaveFileDialog的实现。

The Writer sample application shows how to use them and how the code can be unit tested.

Writer 示例应用程序展示了如何使用它们以及如何对代码进行单元测试。

回答by Daniel Angulo Duque

In my opinion the best solution is creating a custom control.

在我看来,最好的解决方案是创建自定义控件。

The custom control I usually create is composed from:

我通常创建的自定义控件由以下部分组成:

  • Textbox or textblock
  • Button with an image as template
  • String dependency property where the file path will be wrapped to
  • 文本框或文本块
  • 以图片为模板的按钮
  • 文件路径将被包装到的字符串依赖属性

So the *.xaml file would be like this

所以 *.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>

And the *.cs file:

和 *.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;
    }
}

At the end you can bind it to your view model:

最后,您可以将其绑定到您的视图模型:

<controls:customFilePicker Text="{Binding Text}"/>

回答by plainionist

From my perspective the best option is the prism library and InteractionRequests. The action to open the dialog remains within the xaml and gets triggered from Viewmodel while the Viewmodel does not need to know anything about the view.

从我的角度来看,最好的选择是棱镜库和交互请求。打开对话框的操作保留在 xaml 中并从 Viewmodel 触发,而 Viewmodel 不需要了解有关视图的任何信息。

See also

也可以看看

https://plainionist.github.io///Mvvm-Dialogs/

https://plainionist.github.io///Mvvm-Dialogs/

As example see:

例如见:

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/PopupCommonDialogAction.cs

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/PopupCommonDialogAction.cs

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs