Pregunta

Ok, realmente me gustaría saber cómo los desarrolladores expertos de MVVM manejan un diálogo de archivo abierto en WPF.

Realmente no quiero hacer esto en mi ViewModel (donde se hace referencia a 'Examinar' a través de un DelegateCommand)

void Browse(object param)
{
    //Add code here
    OpenFileDialog d = new OpenFileDialog();

    if (d.ShowDialog() == true)
    {
        //Do stuff
    }
}

Porque creo que eso va en contra de la metodología MVVM.

¿Qué hago?

¿Fue útil?

Solución

Lo mejor que puedes hacer aquí es usar un servicio.

Un servicio es solo una clase a la que accede desde un repositorio central de servicios, a menudo un contenedor IOC. Luego, el servicio implementa lo que necesita, como OpenFileDialog.

Entonces, suponiendo que tenga un IFileDialogService en un contenedor de Unity, podría hacer ...

void Browse(object param)
{
    var fileDialogService = container.Resolve<IFileDialogService>();

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}

Otros consejos

Me hubiera gustado comentar una de las respuestas, pero lamentablemente, mi reputación no es lo suficientemente alta como para hacerlo.

Tener una llamada como OpenFileDialog () viola el patrón MVVM porque implica una vista (diálogo) en el modelo de vista. El modelo de vista puede llamar a algo como GetFileName () (es decir, si el enlace simple no es suficiente), pero no debería importarle cómo se obtiene el nombre del archivo.

Uso un servicio que, por ejemplo, puedo pasar al constructor de mi viewModel o resolver mediante inyección de dependencia. por ejemplo,

public interface IOpenFileService
{
    string FileName { get; }
    bool OpenFileDialog()
}

y una clase que lo implementa, usando OpenFileDialog debajo del capó. En viewModel, solo uso la interfaz y, por lo tanto, puedo simularla / reemplazarla si es necesario.

ViewModel no debe abrir diálogos ni siquiera saber de su existencia. Si la VM está alojada en una DLL separada, el proyecto no debería tener una referencia a PresentationFramework.

Me gusta usar una clase auxiliar en la vista para cuadros de diálogo comunes.

La clase auxiliar expone un comando (no un evento) al que se une la ventana en XAML. Esto implica el uso de RelayCommand dentro de la vista. La clase auxiliar es un DepencyObject, por lo que puede vincularse al modelo de vista.

class DialogHelper : DependencyObject
{
    public ViewModel ViewModel
    {
        get { return (ViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(ViewModelProperty_Changed)));

    private static void ViewModelProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (ViewModelProperty != null)
        {
            Binding myBinding = new Binding("FileName");
            myBinding.Source = e.NewValue;
            myBinding.Mode = BindingMode.OneWayToSource;
            BindingOperations.SetBinding(d, FileNameProperty, myBinding);
        }
    }

    private string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }

    private static readonly DependencyProperty FileNameProperty =
        DependencyProperty.Register("FileName", typeof(string), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(FileNameProperty_Changed)));

    private static void FileNameProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("DialogHelper.FileName = {0}", e.NewValue);
    }

    public ICommand OpenFile { get; private set; }

    public DialogHelper()
    {
        OpenFile = new RelayCommand(OpenFileAction);
    }

    private void OpenFileAction(object obj)
    {
        OpenFileDialog dlg = new OpenFileDialog();

        if (dlg.ShowDialog() == true)
        {
            FileName = dlg.FileName;
        }
    }
}

La clase auxiliar necesita una referencia a la instancia de ViewModel. Ver el diccionario de recursos. Justo después de la construcción, se establece la propiedad ViewModel (en la misma línea de XAML). Esto es cuando la propiedad FileName en la clase auxiliar está vinculada a la propiedad FileName en el modelo de vista.

<Window x:Class="DialogExperiment.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DialogExperiment"
        xmlns:vm="clr-namespace:DialogExperimentVM;assembly=DialogExperimentVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <vm:ViewModel x:Key="viewModel" />
        <local:DialogHelper x:Key="helper" ViewModel="{StaticResource viewModel}"/>
    </Window.Resources>
    <DockPanel DataContext="{StaticResource viewModel}">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Open" Command="{Binding Source={StaticResource helper}, Path=OpenFile}" />
            </MenuItem>
        </Menu>
    </DockPanel>
</Window>

Tener un servicio es como abrir una vista desde viewmodel. Tengo a la vista una propiedad de dependencia, y en el intercambio de propiedades, abro FileDialog y leo la ruta, actualizo la propiedad y, en consecuencia, la propiedad vinculada de la máquina virtual

Lo resolví de esta manera:

  • En ViewModel he definido una interfaz y trabajo con ella en ViewModel
  • En Ver he implementado esta interfaz.

CommandImpl no está implementado en el código a continuación.

ViewModel:

namespace ViewModels.Interfaces
{
    using System.Collections.Generic;
    public interface IDialogWindow
    {
        List<string> ExecuteFileDialog(object owner, string extFilter);
    }
}

namespace ViewModels
{
    using ViewModels.Interfaces;
    public class MyViewModel
    {
        public ICommand DoSomeThingCmd { get; } = new CommandImpl((dialogType) =>
        {
            var dlgObj = Activator.CreateInstance(dialogType) as IDialogWindow;
            var fileNames = dlgObj?.ExecuteFileDialog(null, "*.txt");
            //Do something with fileNames..
        });
    }
}

Vista:

namespace Views
{
    using ViewModels.Interfaces;
    using Microsoft.Win32;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;

    public class OpenFilesDialog : IDialogWindow
    {
        public List<string> ExecuteFileDialog(object owner, string extFilter)
        {
            var fd = new OpenFileDialog();
            fd.Multiselect = true;
            if (!string.IsNullOrWhiteSpace(extFilter))
            {
                fd.Filter = extFilter;
            }
            fd.ShowDialog(owner as Window);

            return fd.FileNames.ToList();
        }
    }
}

XAML:

<Window

    xmlns:views="clr-namespace:Views"
    xmlns:viewModels="clr-namespace:ViewModels"
>    
    <Window.DataContext>
        <viewModels:MyViewModel/>
    </Window.DataContext>

    <Grid>
        <Button Content = "Open files.." Command="{Binding DoSomeThingCmd}" CommandParameter="{x:Type views:OpenFilesDialog}"/>
    </Grid>
</Window>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top