我剛開始學習WPF的MVVM模式。我碰壁了:當你需要顯示OpenFileDialog時,你會做什麼?帶有MVVM模式的WPF OpenFileDialog?
這裏有一個例子UI我嘗試使用它:
當單擊瀏覽按鈕,一個OpenFileDialog應該顯示。當用戶從OpenFileDialog中選擇一個文件時,文件路徑應顯示在文本框中。
我該怎麼用MVVM做到這一點?
更新:我如何用MVVM做到這一點,並使其單元測試?下面的解決方案不適用於單元測試。
我剛開始學習WPF的MVVM模式。我碰壁了:當你需要顯示OpenFileDialog時,你會做什麼?帶有MVVM模式的WPF OpenFileDialog?
這裏有一個例子UI我嘗試使用它:
當單擊瀏覽按鈕,一個OpenFileDialog應該顯示。當用戶從OpenFileDialog中選擇一個文件時,文件路徑應顯示在文本框中。
我該怎麼用MVVM做到這一點?
更新:我如何用MVVM做到這一點,並使其單元測試?下面的解決方案不適用於單元測試。
我通常會做的是爲執行此功能的應用程序服務創建一個接口。在我的例子中,我假設你正在使用類似MVVM Toolkit或類似的東西(這樣我可以得到一個基本的ViewModel和一個RelayCommand)。
下面是一個用於完成基本IO操作(如OpenFileDialog和OpenFile)的非常簡單接口的示例。我在這裏向他們展示,所以你不認爲我建議你用一種方法創建一個接口來解決這個問題。
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隔離您作假框架。以下是使用隔離器的相同測試:
[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);
}
希望這也是有幫助的。
這對我來說很有意義:有一些服務可以做這樣的對話,並通過ViewModel中的一個接口使用該服務。非常好,謝謝。 (p.s.我將用RhinoMocks進行測試,僅供參考,但我可以認爲這部分沒有問題。) –
Shucks。在這裏,我認爲我很喜歡。很高興我能幫上忙。 –
在第二段FYI :)小錯字:)。感謝你的回答! – Jeff
首先,我會建議你從WPF MVVM toolkit開始。這給你一個很好的用於你的項目的命令選擇。自從MVVM模式引入以來,一個特別的功能就是RelayCommand(當然還有其他版本,但我只是堅持使用最常用的)。它是一個ICommand接口的實現,它允許你在你的ViewModel中創建一個新的命令。
回到你的問題,這裏是你的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();
那就是它。
隨着您對這些命令的熟悉程度的提高,您還可以設置條件,以便您希望禁用「瀏覽」按鈕等。我希望能夠指引您朝着您想要的方向行進。
我應該改寫一下:我如何使這個單元可測試?運行單元測試時,您的解決方案會彈出一個對話框。 –
如果您的ViewModel存放在它自己的DLL中,它不應該引用PresentationFramework.dll。 –
爲什麼使用'RegisterCommands();'而不是直接寫'OpenCommand = new RelayCommand(ExecuteOpenFileDialog);'? – aloisdg
WPF Application Framework (WAF)爲Open和SaveFileDialog提供了一個實現。
Writer示例應用程序顯示如何使用它們以及如何對代碼進行單元測試。
在我看來最好的解決方案是創建一個自定義控件。
定製控制我通常創建從由:
所以* .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}"}/>
從我的角度來看,最好的選擇是棱鏡圖書館和InteractionRequests。打開對話框的操作保留在xaml中,並從Viewmodel觸發,而Viewmodel不需要知道視圖的任何內容。
也
https://plainionist.github.io///Mvvm-Dialogs/
看到的例子中看到:
這是一個重複:http://stackoverflow.com/questions/1043918/ open-file-dialog-mvvm –
當然可以。 :-)在發佈這個問題之前,我在SO上做了一些搜索,但沒有出現。好吧。 –
我投票結束這個問題,因爲它是一個確切的副本。 –