Ouvrir la boîte de dialogue de fichier MVVM

Ok, je voudrais vraiment savoir comment les développeurs MVVM experts gèrent une boîte de dialogue openfile dans WPF.

Je ne veux pas vraiment faire cela dans mon ViewModel (où 'Browse' est référencé via une DelegateCommand)

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

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

Parce que je crois que cela va à l'encontre de la méthodologie MVVM.

Que dois-je faire?

41
demandé sur BoltClock 2009-06-25 17:26:49

6 réponses

La meilleure chose à faire ici est d'utiliser un service.

Un service est juste une classe à laquelle vous accédez à partir d'un référentiel central de services, souvent un conteneur IOC. Le service implémente ensuite ce dont vous avez besoin comme OpenFileDialog.

Donc, en supposant que vous avez un IFileDialogService dans un conteneur Unity, vous pouvez le faire...

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

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}
32
répondu Cameron MacFarland 2015-06-19 08:21:52

J'aurais aimé commenter l'une des réponses, mais hélas, ma réputation n'est pas assez élevée pour le faire.

Avoir un appel tel que OpenFileDialog() viole le modèle MVVM car il implique une vue (dialogue) dans le modèle de vue. Le modèle de vue peut appeler quelque chose comme GetFileName () (c'est-à-dire, si une simple liaison n'est pas suffisante), mais il ne devrait pas se soucier de la façon dont le nom de fichier est obtenu.

7
répondu JAB 2013-10-17 14:28:49

J'utilise un service que je peux par exemple passer dans le constructeur de mon viewModel ou résoudre via une injection de dépendance. par exemple

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

Et une classe l'implémentant, en utilisant OpenFileDialog sous le capot. Dans le viewModel, j'utilise uniquement l'interface et je peux donc la moquer / la Remplacer si nécessaire.

6
répondu Botz3000 2015-06-19 08:22:00

Le ViewModel ne doit pas ouvrir les boîtes de dialogue ni même connaître leur existence. Si la machine virtuelle est logée dans une DLL distincte, le projet ne doit pas avoir de référence à PresentationFramework.

J'aime utiliser une classe d'aide dans la vue pour les boîtes de dialogue communes.

La classe helper expose une commande (pas un événement) à laquelle la fenêtre se lie en XAML. Cela implique L'utilisation de RelayCommand dans la vue. La classe helper est un DepencyObject donc elle peut se lier à la vue modèle.

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 a besoin d'une référence à L'instance ViewModel. Voir le dictionnaire de ressources. Juste après la construction, la propriété ViewModel est définie (dans la même ligne de XAML). C'est lorsque la propriété FileName de la classe helper est liée à la propriété FileName du modèle de vue.

<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>
6
répondu Paul Williams 2015-06-19 08:22:08

Avoir un service, c'est comme ouvrir une vue depuis viewmodel. J'ai une propriété de dépendance en vue, et sur le chnage de la propriété, j'ouvre FileDialog et lis le chemin, met à jour la propriété et par conséquent la propriété liée de la VM

2
répondu Jilt 2010-03-08 14:17:14

Je l'ai résolu pour moi de cette façon:

  • Dans ViewModel j'ai défini une interface et de travailler avec elle dans ViewModel
  • Dans View j'ai implémenté cette interface.

Commanddimpl n'est pas implémenté dans le code ci-dessous.

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

Vue:

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>
1
répondu Rekshino 2017-05-03 09:28:40