103

WPF の MVVM パターンの学習を始めたところです。壁にぶつかった:を表示する必要がある場合はどうしますOpenFileDialogか?

これを使用しようとしているUIの例を次に示します。

代替テキスト

参照ボタンをクリックすると、OpenFileDialogが表示されます。ユーザーが からファイルを選択するOpenFileDialogと、ファイル パスがテキスト ボックスに表示されます。

MVVMでこれを行うにはどうすればよいですか?

更新: MVVM でこれを実行し、単体テスト可能にするにはどうすればよいですか? 以下の解決策は、単体テストでは機能しません。

4

5 に答える 5

102

私が通常行うことは、この機能を実行するアプリケーション サービスのインターフェイスを作成することです。私の例では、MVVM Toolkit などを使用していると仮定します (したがって、基本 ViewModel と を取得できますRelayCommand)。

OpenFileDialogや などの基本的な IO 操作を行うための非常に単純なインターフェイスの例を次に示しますOpenFile。ここでは両方を示しているので、この問題を回避するために 1 つのメソッドで 1 つのインターフェイスを作成することを提案しているとは思わないでください。

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

これも役立つことを願っています。

于 2009-10-26T03:48:38.420 に答える
5

WPFアプリケーションフレームワーク(WAF)は、OpenおよびSaveFileDialogの実装を提供します。

Writerサンプルアプリケーションは、それらの使用方法とコードの単体テスト方法を示しています。

于 2009-10-26T08:39:24.230 に答える
2

私の観点からすると、最善の選択肢はプリズム ライブラリと InteractionRequests です。ダイアログを開くアクションは xaml 内に残り、Viewmodel からトリガーされますが、Viewmodel はビューについて何も知る必要はありません。

こちらもご覧ください

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

例として、次を参照してください。

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

于 2018-01-18T17:54:10.950 に答える
1

私の意見では、最善の解決策はカスタム コントロールを作成することです。

私が通常作成するカスタム コントロールは、次のものから構成されます。

  • テキストボックスまたはテキストブロック
  • 画像をテンプレートにしたボタン
  • ファイル パスがラップされる文字列依存関係プロパティ

したがって、*.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}"/>
于 2016-06-24T09:28:44.310 に答える