我刚开始学习WPF的MVVM模式。 我碰了壁: 当你需要展示一个OpenFileDialog你会怎么做 ?
下面是我想要使用它的一个例子UI:
当点击浏览按钮,一个OpenFileDialog应该显示。 当用户选择从一个的OpenFileDialog文件时,文件路径应该被显示在文本框中。
我如何与MVVM做到这一点?
更新 :我怎样才能做到这一点与MVVM并使其单元测试,能够? 该解决方案下面没有单元测试工作。
我刚开始学习WPF的MVVM模式。 我碰了壁: 当你需要展示一个OpenFileDialog你会怎么做 ?
下面是我想要使用它的一个例子UI:
当点击浏览按钮,一个OpenFileDialog应该显示。 当用户选择从一个的OpenFileDialog文件时,文件路径应该被显示在文本框中。
我如何与MVVM做到这一点?
更新 :我怎样才能做到这一点与MVVM并使其单元测试,能够? 该解决方案下面没有单元测试工作。
我通常做的是创建一个执行此功能的应用服务的接口。 在我的例子中我假设你正在使用类似的工具包MVVM或类似的东西(这样我就可以得到基本视图模型和RelayCommand)。
这里有一个非常简单的界面,做这样的OpenFileDialog和基本的OpenFile IO操作的一个例子。 我展示他们都在这里,所以你不要以为我建议你创建一个接口与一个方法来解决这个问题。
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;
}
}
}
所以这是非常简单的。 现在到了最后一部分:可测试性。 这应该是显而易见的,但我会告诉你如何做一个简单的测试此。 我用的起订量为磕碰,但你可以使用任何你想当然。
[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 )可能挽救你不必做了很多这种东西的。 它看起来像目前还不支持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);
}
希望这是有帮助。
在WPF应用程序框架(WAF)提供了开放式和SaveFileDialog的实现。
作家示例应用程序显示如何使用它们,以及如何可以将代码单元进行测试。
首先,我建议你用开始WPF MVVM工具包 。 这给你的命令不错的选择使用为您的项目。 因为MVVM模式的介绍已经提出了著名的特殊功能之一是RelayCommand(当然还有其他曼尼版本,但我只是坚持最常用的)。 其ICommand接口,让您箱子一个新的命令在您的视图模型的实现。
回到你的问题,这里是你的视图模型可能是什么样子的例子。
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内会从视图模型触发而视图模型并不需要了解该视图什么。
也可以看看
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
在我看来,最好的解决方法是创建一个自定义的控制。
自定义控制我通常创建由组成:
所以*的.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}"/>