web-dev-qa-db-ja.com

MVVMパターンを持つWPF OpenFileDialog?

WPFのMVVMパターンの学習を始めました。私は壁にぶつかりました:OpenFileDialogを表示する必要があるときに何をしますか

以下は、私が使用しようとしているUIの例です。

alt text

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

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

更新:MVVMでこれを実行し、ユニットテスト可能にするにはどうすればよいですか?以下のソリューションは、単体テストでは機能しません。

94

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

以下は、OpenFileDialogやOpenFileのような基本的なIO操作を行うための非常にシンプルなインターフェイスの例です。ここでは両方を示しているため、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 )というライブラリがあり、lotこの種のもの。 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);
}

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

90
Anderson Imes

WPF Application Framework(WAF) は、OpenおよびSaveFileDialogの実装を提供します。

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

4
jbe

私の観点から、最良のオプションはプリズムライブラリと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

2
plainionist

まず、 WPF MVVMツールキット で始めることをお勧めします。これにより、プロジェクトで使用するコマンドの選択肢が増えます。 MVVMパターンの導入以来有名になった特定の機能の1つに、RelayCommandがあります(もちろん、他にも多くのバージョンがありますが、最もよく使用されるものに固執しています)。 ViewModelで新しいコマンドを作成できるICommandインターフェイスの実装です。

質問に戻って、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;
    }
}

ViewModelBaseおよびRelayCommandは両方とも MVVM Toolkit 。 XAMLは次のようになります。

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

xAML.CSコードビハインド。

DataContext = new OpenFileDialogVM();
InitializeComponent();

それでおしまい。

コマンドに慣れてきたら、[参照]ボタンを無効にするタイミングなどの条件を設定することもできます。希望する方向を示していただければ幸いです。

2
Tri Q Tran

私の意見では、最適なソリューションはカスタムコントロールを作成することです。

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

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

したがって、*。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}"/>
1