Question

Je viens de commencer à apprendre le modèle MVVM pour WPF. Je frappe un mur: que faites-vous lorsque vous devez afficher un OpenFileDialog ?

Voici un exemple d'interface utilisateur sur laquelle j'essaie de l'utiliser:

alt text

Lorsque le bouton de navigation est cliqué, un OpenFileDialog devrait être affiché. Lorsque l'utilisateur sélectionne un fichier dans OpenFileDialog, le chemin du fichier doit être affiché dans la zone de texte.

Comment puis-je faire cela avec MVVM?

Mise à jour : comment puis-je procéder avec MVVM et le rendre testable? La solution ci-dessous ne fonctionne pas pour les tests unitaires.

Était-ce utile?

La solution

Ce que je fais généralement est de créer une interface pour un service d’application qui remplit cette fonction. Dans mes exemples, je supposerai que vous utilisez quelque chose comme le MVVM Toolkit ou quelque chose de similaire (pour que je puisse obtenir un ViewModel de base et une RelayCommand).

Voici un exemple d'interface extrêmement simple permettant d'effectuer des opérations d'E / S de base, telles que OpenFileDialog et OpenFile. Je leur montre les deux ici pour que vous ne pensiez pas que je vous suggère de créer une interface avec une seule méthode pour résoudre ce problème.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

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

Dans votre application, vous devez fournir une implémentation par défaut de ce service. Voici comment vous le consommeriez.

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

Donc, c'est assez simple. Passons maintenant à la dernière partie: la testabilité. Celui-ci devrait être évident, mais je vais vous montrer comment faire un test simple pour cela. J'utilise Moq pour le talonnage, mais vous pouvez utiliser ce que vous voulez bien sûr.

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

Cela fonctionnera probablement pour vous.

Il existe une bibliothèque sur CodePlex appelée "SystemWrapper". ( http://systemwrapper.codeplex.com ) qui pourrait vous éviter de devoir faire un lot de ce genre de chose. Il semblerait que FileDialog ne soit pas encore supporté. Vous devrez donc écrire une interface pour celui-là.

J'espère que cela vous aidera.

Modifier :

Il semble que je me souvienne de votre préférence pour TypeMock Isolator pour votre structure fictive. Voici le même test avec Isolator:

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

J'espère que cela vous sera utile également.

Autres conseils

Le WPF Application Framework (WAF) fournit une implémentation pour Open et SaveFileDialog.

L'exemple d'application Writer montre comment les utiliser et comment tester le code par unité.

Tout d'abord, je vous recommande de commencer avec un Boîte à outils WPF MVVM . Cela vous donne une belle sélection de commandes à utiliser pour vos projets. Une fonction particulière qui est devenue célèbre depuis l’introduction du modèle MVVM est la RelayCommand (il existe bien d’autres versions bien sûr, mais j’en reste à la plus utilisée). C’est une implémentation de l’interface ICommand qui vous permet de créer une nouvelle commande dans votre ViewModel.

Revenons à votre question. Voici un exemple de ce à quoi votre ViewModel pourrait ressembler.

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 et RelayCommand proviennent tous deux de MVVM Toolkit . Voici à quoi peut ressembler le XAML.

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

et votre code XAML.CS derrière.

DataContext = new OpenFileDialogVM();
InitializeComponent();

C'est tout.

Au fur et à mesure que vous maîtriserez mieux les commandes, vous pourrez également définir les conditions de désactivation du bouton Parcourir, etc. J'espère que cela vous a orienté dans la direction souhaitée.

De mon point de vue, la meilleure option est la bibliothèque de prismes et InteractionRequests. L'action permettant d'ouvrir la boîte de dialogue reste dans le xaml et est déclenchée à partir de Viewmodel, tandis que Viewmodel n'a pas besoin de connaître la vue.

Voir aussi

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

À titre d'exemple, voir:

https: // github .com / plainionist / Plainion.Prism / blob / master / src / Plainion.Prism / Interactivité / PopupCommonDialogAction.cs

https: / /github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs

À mon avis, la meilleure solution consiste à créer un contrôle personnalisé.

Le contrôle personnalisé que je crée habituellement est composé à partir de:

  • Zone de texte ou bloc de texte
  • Bouton avec une image comme modèle
  • Propriété de dépendance de chaîne dans laquelle le chemin du fichier sera encapsulé

Ainsi, le fichier * .xaml serait comme ceci

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

Et le fichier * .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;
    }
}

À la fin, vous pouvez le lier à votre modèle de vue:

<controls:customFilePicker Text="{Binding Text}"/>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top