Pregunta

Acabo de comenzar a aprender el patrón MVVM para WPF. Golpeo una pared: ¿qué haces cuando necesitas mostrar un OpenFileDialog ?

Aquí hay un ejemplo de interfaz de usuario en el que trato de usarlo:

alt text

Cuando se hace clic en el botón Examinar, se debe mostrar un OpenFileDialog. Cuando el usuario selecciona un archivo de OpenFileDialog, la ruta del archivo debe mostrarse en el cuadro de texto.

¿Cómo puedo hacer esto con MVVM?

Actualización : ¿Cómo puedo hacer esto con MVVM y hacer que la unidad pueda probarse? La solución a continuación no funciona para las pruebas unitarias.

¿Fue útil?

Solución

Lo que generalmente hago es crear una interfaz para un servicio de aplicación que realiza esta función. En mis ejemplos, asumiré que estás usando algo como MVVM Toolkit o algo similar (para que pueda obtener un ViewModel base y un RelayCommand).

Este es un ejemplo de una interfaz extremadamente simple para realizar operaciones básicas de IO, como OpenFileDialog y OpenFile. Les muestro ambos aquí, así que no creo que esté sugiriendo que cree una interfaz con un método para solucionar este problema.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

     //Other similar untestable IO operations
     Stream OpenFile(string path);
}

En su aplicación, proporcionaría una implementación predeterminada de este servicio. Así es como lo consumirías.

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;
          }
     }
}

Eso es bastante simple. Ahora para la última parte: la testabilidad. Este debería ser obvio, pero te mostraré cómo hacer una prueba simple para esto. Utilizo Moq para apagar, pero puedes usar lo que quieras, por supuesto.

[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);
}

Esto probablemente funcionará para ti.

Hay una biblioteca en CodePlex llamada " SystemWrapper " ( http://systemwrapper.codeplex.com ) que podría evitar que tenga que hacer un lote de este tipo de cosas. Parece que FileDialog no es compatible todavía, por lo que definitivamente tendrá que escribir una interfaz para esa.

Espero que esto ayude.

Editar :

Me parece recordar que preferías el aislador TypeMock para tu marco falso. Aquí está la misma prueba usando aislador:

[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);
}

Espero que esto también sea útil.

Otros consejos

El WPF Application Framework (WAF) proporciona una implementación para Open y Guardar archivo de diálogo.

La aplicación de ejemplo de Writer muestra cómo usarlos y cómo se puede probar el código de la unidad.

Primero, te recomendaría que comiences con un Kit de herramientas WPF MVVM . Esto le brinda una buena selección de Comandos para usar en sus proyectos. Una característica particular que se ha hecho famosa desde la introducción del patrón MVVM es el RelayCommand (hay muchas otras versiones, por supuesto, pero me limito a las más utilizadas). Es una implementación de la interfaz ICommand que le permite crear un nuevo comando en su ViewModel.

Volviendo a su pregunta, aquí hay un ejemplo de cómo podría ser su 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 y RelayCommand son de MVVM Toolkit . Aquí es cómo puede verse el XAML.

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

y su código XAML.CS detrás.

DataContext = new OpenFileDialogVM();
InitializeComponent();

Eso es todo.

A medida que se familiarice con los comandos, también puede establecer las condiciones de cuándo desea que se deshabilite el botón Examinar, etc. Espero que le indiquen la dirección que desea.

Desde mi perspectiva, la mejor opción es la biblioteca de prismas e InteractionRequests. La acción para abrir el cuadro de diálogo permanece dentro del xaml y se activa desde Viewmodel, mientras que Viewmodel no necesita saber nada sobre la vista.

Ver también

https://plainionist.github.io///Mvvm-Dialogs/

Como ejemplo ver:

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

En mi opinión, la mejor solución es crear un control personalizado.

El control personalizado que normalmente creo se compone de:

  • Cuadro de texto o bloque de texto
  • Botón con una imagen como plantilla
  • propiedad de dependencia de cadena donde la ruta del archivo se ajustará a

Entonces, el archivo * .xaml sería así

<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>

Y el archivo * .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;
    }
}

Al final, puedes enlazarlo a tu modelo de vista:

<controls:customFilePicker Text="{Binding Text}"/>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top