Question

Ok, j'aimerais vraiment savoir comment les développeurs experts MVVM traitent un dialogue openfile dans WPF.

Je ne souhaite pas vraiment faire cela dans mon ViewModel (où "Parcourir" est référencé via une commande Delegate)

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

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

Parce que je pense que cela va à l’encontre de la méthodologie MVVM.

Qu'est-ce que je fais?

Était-ce utile?

La solution

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

Un service est simplement une classe à laquelle vous accédez depuis 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 ayez un IFileDialogService dans un conteneur Unity, vous pourriez faire ...

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

    string path = fileDialogService.OpenFileDialog();

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

Autres conseils

J'aurais aimé commenter l'une des réponses mais, hélas, ma réputation n'est pas assez grande 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 préoccuper de la façon dont le nom de fichier est obtenu.

J'utilise un service que je peux par exemple transmettre au 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, je n'utilise que l'interface et je peux donc la simuler / la remplacer si nécessaire.

Le modèle de vue ne doit pas ouvrir de boîte de dialogue ni même connaître son existence. Si la machine virtuelle est hébergée dans une DLL distincte, le projet ne doit pas comporter de référence à PresentationFramework.

J'aime utiliser une classe d'assistance dans la vue pour les dialogues courants.

La classe d'assistance expose une commande (et non un événement) à laquelle la fenêtre se lie en XAML. Cela implique l'utilisation de RelayCommand dans la vue. La classe d'assistance est un DepencyObject afin qu'elle puisse se lier au modèle de vue.

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 d'assistance 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 à ce moment que la propriété FileName de la classe d'assistance 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>

Avoir un service revient à ouvrir une vue de viewmodel. J'ai une propriété Dependency en vue, et sur le chnage de la propriété, j'ouvre FileDialog et lis le chemin, mets à jour la propriété et par conséquent la propriété liée de la VM

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

  • Dans ViewModel , j'ai défini une interface et je travaille avec elle. ViewModel
  • Dans Affichage , j'ai implémenté cette interface.

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

Voir:

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>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top