Domanda

Ok, vorrei davvero sapere come gli sviluppatori esperti di MVVM gestiscono una finestra di dialogo openfile in WPF.

Non voglio davvero farlo nel mio ViewModel (dove si fa riferimento a 'Sfoglia' tramite un comando delegato)

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

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

Perché credo che vada contro la metodologia MVVM.

Cosa devo fare?

È stato utile?

Soluzione

La cosa migliore da fare qui è usare un servizio.

Un servizio è solo una classe a cui si accede da un repository centrale di servizi, spesso un contenitore IOC. Il servizio implementa quindi ciò di cui hai bisogno come OpenFileDialog.

Quindi, supponendo che tu abbia un IFileDialogService in un contenitore Unity, potresti fare ...

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

    string path = fileDialogService.OpenFileDialog();

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

Altri suggerimenti

Mi sarebbe piaciuto commentare una delle risposte, ma purtroppo la mia reputazione non è abbastanza elevata per farlo.

La ricezione di una chiamata come OpenFileDialog () viola il modello MVVM perché implica una vista (finestra di dialogo) nel modello di vista. Il modello di visualizzazione può chiamare qualcosa come GetFileName () (ovvero, se l'associazione semplice non è sufficiente), ma non dovrebbe interessare come si ottiene il nome del file.

Uso un servizio che, ad esempio, posso passare nel costruttore del mio viewModel o risolvere tramite l'iniezione delle dipendenze. per es.

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

e una classe che lo implementa, usando OpenFileDialog sotto il cofano. In viewModel, utilizzo solo l'interfaccia e quindi posso deriderla / sostituirla se necessario.

ViewModel non dovrebbe aprire finestre di dialogo o nemmeno sapere della loro esistenza. Se la VM è ospitata in una DLL separata, il progetto non dovrebbe avere un riferimento a PresentationFramework.

Mi piace usare una classe helper nella vista per dialoghi comuni.

La classe helper espone un comando (non un evento) a cui la finestra si lega in XAML. Ciò implica l'uso di RelayCommand all'interno della vista. La classe helper è un DepencyObject in modo che possa legarsi al modello di visualizzazione.

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 classe helper necessita di un riferimento all'istanza ViewModel. Vedi il dizionario delle risorse. Subito dopo la costruzione, viene impostata la proprietà ViewModel (nella stessa linea di XAML). Questo è quando la proprietà FileName sulla classe helper è associata alla proprietà FileName sul modello di visualizzazione.

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

Avere un servizio è come aprire una vista dal viewmodel. Ho una proprietà Dependency in vista e sul chnage della proprietà, apro FileDialog e leggo il percorso, aggiorno la proprietà e di conseguenza la proprietà associata della VM

L'ho risolto per me in questo modo:

  • In ViewModel ho definito un'interfaccia e ci lavoro dentro ViewModel
  • In Visualizza ho implementato questa interfaccia.

CommandImpl non è implementato nel codice seguente.

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

Visualizza:

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>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top