WPF OpenFileDialog com o padrão MVVM?
-
06-07-2019 - |
Pergunta
Eu só comecei a aprender o padrão MVVM para WPF. Eu bati em uma parede:? o que você faz quando você precisa mostrar um OpenFileDialog
Aqui está um exemplo UI Eu estou tentando usá-lo em:
Quando o botão de navegação é clicado, um OpenFileDialog deve ser mostrado. Quando o usuário seleciona um arquivo do OpenFileDialog, o caminho do arquivo deve ser exibido na caixa de texto.
Como posso fazer isso com MVVM?
Atualizar : Como posso fazer isso com MVVM e torná-lo unidade de teste-poder? A solução abaixo não funciona para testes de unidade.
Solução
O que eu geralmente faço é criar uma interface para um serviço de aplicativo que executa esta função. Nos meus exemplos eu vou assumir que você está usando algo parecido com o MVVM Toolkit ou coisa semelhante (para que eu possa obter uma base ViewModel e uma RelayCommand).
Aqui está um exemplo de uma interface extremamente simples para fazer operações básicas de IO como OpenFileDialog e OpenFile. Eu estou mostrando-lhes tanto aqui, assim você não acha que eu estou sugerindo que você criar uma interface com um método para contornar este problema.
public interface IOService
{
string OpenFileDialog(string defaultPath);
//Other similar untestable IO operations
Stream OpenFile(string path);
}
Em seu aplicativo, você deve fornecer uma implementação padrão desse serviço. Aqui está como você consumi-lo.
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;
}
}
}
Então, isso é muito simples. Agora para a última parte: a capacidade de teste. Este deve ser óbvio, mas eu vou lhe mostrar como fazer um teste simples para isso. Eu uso Moq para stubbing, mas você pode usar o que quiser, é claro.
[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);
}
Este provavelmente trabalho para você.
Há um fora biblioteca no CodePlex chamado "SystemWrapper" ( http://systemwrapper.codeplex.com ) que pode salvá-lo de ter que fazer um muito desse tipo de coisa. Parece que FileDialog ainda não é suportado, então você definitivamente tem que escrever uma interface para isso.
Espero que isso ajude.
Editar :
Eu me lembro de você favorecendo TypeMock Isolador para o seu quadro de falsificar. Aqui está o mesmo teste usando Isolador:
[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);
}
Hope isso é útil também.
Outras dicas
O WPF Application Framework (WAF) fornece uma implementação para o Aberto e SaveFileDialog.
A amostra escritor mostra aplicações como usá-los e como o código pode ser unidade testada.
Em primeiro lugar eu recomendo que você começar com um WPF MVVM Toolkit . Isto dá-lhe uma boa selecção de comandos para usar para seus projetos. Uma característica particular que tem sido feito famosa desde a introdução do padrão MVVM é o RelayCommand (há Manny outras versões do curso, mas eu só manter o mais comumente usado). Seu uma implementação da interface ICommand que lhe permite caixote um novo comando em seu ViewModel.
De volta à sua pergunta, aqui está um exemplo do que o seu ViewModel pode parecer.
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 e RelayCommand são ambos do MVVM Toolkit . Aqui está o que o XAML pode parecer.
<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>
e seu código por trás XAML.CS.
DataContext = new OpenFileDialogVM();
InitializeComponent();
É isso.
Como você ficar mais familiarizado com os comandos, você também pode definir as condições de quando você deseja que o botão Procurar para ser desativada, etc. Espero que você apontou na direção que você queria.
Da minha perspectiva, a melhor opção é a biblioteca prisma e InteractionRequests. A ação para abrir os restos de diálogo dentro do XAML e é acionado a partir viewmodel enquanto o ViewModel não precisa saber nada sobre a vista.
Veja também
https://plainionist.github.io///Mvvm-Dialogs/
Como exemplo, consulte:
Na minha opinião, a melhor solução é criar um controle personalizado.
O controle personalizado Eu costumo criar é composto de:
- Caixa de texto ou textblock
- O botão com uma imagem como modelo
- Cordas propriedade de dependência onde o caminho do arquivo será embrulhado para
Então, o arquivo XAML * seria assim
<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>
E o ficheiro.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;
}
}
No final você pode vinculá-lo a seu modelo de vista:
<controls:customFilePicker Text="{Binding Text}"/>