MVVMパターンを持つWPF OpenFileDialog?
-
06-07-2019 - |
質問
WPFのMVVMパターンの学習を始めました。壁にぶつかった: OpenFileDialogを表示する必要があるときはどうしますか?
これを使用しようとしているUIの例を次に示します。
参照ボタンをクリックすると、OpenFileDialogが表示されます。ユーザーがOpenFileDialogからファイルを選択すると、テキストボックスにファイルパスが表示されます。
MVVMでこれを行うにはどうすればよいですか?
更新:MVVMでこれを行い、ユニットテスト可能にする方法を教えてください。以下のソリューションは、単体テストでは機能しません。
解決
私が一般的に行うことは、この機能を実行するアプリケーションサービスのインターフェイスを作成することです。私の例では、MVVM Toolkitなどのようなものを使用していると仮定します(したがって、ベースのViewModelとRelayCommandを取得できます)。
これは、OpenFileDialogやOpenFileなどの基本的なIO操作を行うための非常にシンプルなインターフェイスの例です。ここに両方を表示しているので、この問題を回避するために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);
}
これはおそらくあなたのために働くでしょう。
「SystemWrapper」というCodePlexのライブラリがあります。 ( 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);
}
これも役立つことを願っています。
他のヒント
WPFアプリケーションフレームワーク(WAF) は、OpenおよびSaveFileDialog。
Writerサンプルアプリケーションは、それらの使用方法とコードのユニットテスト方法を示しています。
まず、 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ツールキット。 XAMLは次のようになります。
<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>
およびXAML.CSコードビハインド。
DataContext = new OpenFileDialogVM();
InitializeComponent();
それで。
コマンドに慣れてきたら、[参照]ボタンを無効にするタイミングなどの条件を設定することもできます。希望する方向を示していただければ幸いです。
私の観点からすると、最良のオプションはプリズムライブラリとInteractionRequestsです。ダイアログを開くアクションはxaml内に残り、Viewmodelはビューについて何も知る必要がない間、Viewmodelからトリガーされます。
参照
https://plainionist.github.io///Mvvm-Dialogs/
例として参照してください:
私の意見では、最良の解決策はカスタムコントロールを作成することです。
私が通常作成するカスタムコントロールは、次から構成されています:
- テキストボックスまたはテキストブロック
- テンプレートとして画像を使用したボタン
- ファイルパスがラップされる文字列依存関係プロパティ
したがって、*。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}"/>