MVVM Madness: commandes

J’aime le MVVM. Je n’aime pas ça, mais j’aime ça. La plupart de cela a du sens. Mais, je continue à lire des articles qui vous encouragent à écrire beaucoup de code afin que vous puissiez écrire XAML et ne pas avoir à écrire de code dans le code-behind.

Laisse moi te donner un exemple.

Récemment, je voulais twigr une commande dans mon ViewModel à un ListView MouseDoubleClickEvent. Je ne savais pas trop comment faire ça. Heureusement, Google a des réponses pour tout. J’ai trouvé les articles suivants:

  • http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
  • http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html
  • http://sachabarber.net/?p=514
  • http://geekswithblogs.net/HouseOfBilz/archive/2009/08/27/adventures-in-mvvm-ndash-binding-commands-to-any-event.aspx
  • http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

Bien que les solutions aient été utiles dans ma compréhension des commandes, il y avait des problèmes. Certaines des solutions mentionnées ci-dessus ont rendu le concepteur WPF inutilisable en raison d’un piratage commun consistant à append «Interne» après une propriété de dépendance; le concepteur WPF ne peut pas le trouver, mais le CLR peut le faire. Certaines solutions n’autorisaient pas plusieurs commandes sur le même contrôle. Certaines solutions n’autorisaient pas les parameters.

Après quelques heures d’expérimentation, j’ai décidé de le faire:

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { ListView lv = sender as ListView; MyViewModel vm = this.DataContext as MyViewModel; vm.DoSomethingCommand.Execute(lv.SelectedItem); } 

Alors, puristes MVVM, s’il vous plaît dites-moi ce qui ne va pas avec ça? Je peux toujours tester mon commande. Cela semble très pratique, mais semble violer la ligne direcsortingce de “ZOMG … vous avez du code dans votre code-behind !!!!” S’il vous plaît partager vos pensées.

Merci d’avance.

Je pense que la faute réside dans l’exigence de pureté. Les modèles de conception, MVVM inclus, sont un outil dans la boîte à outils, pas une fin en soi. Si cela a plus de sens de rompre avec la pureté du modèle pour un cas bien réfléchi (et il semble bien que vous ayez bien considéré ce cas), alors rompez avec le modèle.

Si cela fonctionne pour vous et que vous ne croyez pas qu’il s’agit d’un fardeau indu, alors je dirais que rien ne va pas avec ce que vous avez fait. Je pense que vous avez clairement fait la preuve de la nécessité de montrer qu’il s’agit d’une solution raisonnable à votre problème malgré l’implémentation d’une simple MVVM.

(Je considère cet argument similaire aux arguments pour les langages multiparadigmes. Bien qu’une approche Pure OO puisse être appliquée, il est parfois plus approprié de faire les choses de manière plus fonctionnelle. Bien qu’une approche Pure Functional puisse être appliquée, les compromis montrent parfois que OO les techniques sont plus que valables.)

Je suis d’accord avec vous pour dire que de nombreuses solutions MVVM-Command sont trop compliquées. Personnellement, j’utilise une approche mixte et définit mes commandes dans la vue plutôt que dans ViewModel, en utilisant des méthodes et des propriétés de ViewModel.

XAML:

       

Code (View):

 Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2) End Sub Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed myViewModel.LookupAddress() End Sub 

Ce n’est pas du pur MVVM, mais c’est simple, ça marche, il n’a pas besoin de classes de commande spéciales MVVM et cela rend votre code beaucoup plus facile à lire pour les experts non-MVVM (= mes collègues).

Bien que je préfère ne pas écrire code-behind lors de l’utilisation du pattern MVVM, je pense qu’il convient de le faire tant que ce code est purement lié à l’interface utilisateur.

Mais ce n’est pas le cas ici: vous appelez une commande de modèle de vue depuis le code-behind, elle n’est donc pas liée à l’interface utilisateur et la relation entre la vue et la commande de modèle de vue n’est pas directement visible dans XAML.

Je pense que vous pourriez facilement le faire dans XAML, en utilisant le comportement de la commande attachée . De cette façon, vous pouvez “lier” l’événement MouseDoubleClick à une commande de votre modèle de vue:

     ...  

Vous pouvez également accéder facilement à l’élément sélectionné de ListView sans vous y référer directement, à l’aide de l’interface ICollectionView :

 private ICommand _doSomething; public ICommand DoSomething { get { if (_doSomething == null) { _doSomething = new DelegateCommand( () => { ICollectionView view = CollectionViewSource.GetDefaultView(Items); object selected = view.CurrentItem; DoSomethingWithItem(selected); }); } return _doSomething; } } 

Je crois que le but d’avoir “No code in the code-behind” est exactement cela, un objective à atteindre – pas quelque chose que vous devriez considérer comme un dogme absolu. Il y a des endroits appropriés pour le code dans la vue – et ce n’est pas nécessairement un mauvais exemple de où et comment le code peut être plus simple qu’une approche alternative.

L’avantage des autres approches que vous répertoriez, y compris les propriétés attachées ou les événements associés, est qu’elles sont réutilisables. Lorsque vous connectez un événement directement, faites ce que vous avez fait, il est très facile de dupliquer ce code dans votre application. En créant une propriété ou un événement attaché unique pour gérer ce câblage, vous ajoutez du code supplémentaire dans la plomberie – mais c’est du code qui est réutilisable pour tout composant ListView sur lequel vous souhaitez un double-clic.

Cela dit, j’ai tendance à préférer une approche plus “puriste”. La gestion de tous les événements en dehors de View peut ne pas affecter le scénario de test (auquel vous êtes spécifiquement confronté), mais elle affecte la conception et la maintenabilité globales. En introduisant du code dans votre code, vous restreignez votre affichage à toujours utiliser ListView avec le gestionnaire d’événements – ce qui lie votre vue à du code et limite la flexibilité de conception par un concepteur.

Ce que @JP décrit dans la question initiale et @Heinzi mentionne la réponse est une approche pragmatique du traitement des commandes difficiles. Utiliser un tout petit code de gestion des événements dans le code est particulièrement utile lorsque vous devez effectuer un petit travail sur l’interface utilisateur avant d’appeler la commande.

Considérons le cas classique de OpenFileDialog. Il est beaucoup plus facile d’utiliser un événement de clic sur le bouton, d’afficher la boîte de dialog, puis d’envoyer les résultats à une commande sur votre ViewModel plutôt que d’adopter les routines de messagerie complexes utilisées par les kits d’outils MVVM.

Dans votre XAML:

  

Dans votre code derrière:

  private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e) { // Configure open file dialog box Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); dlg.FileName = "Document"; // Default file name dlg.DefaultExt = ".txt"; // Default file extension dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension // Show open file dialog box bool? result = dlg.ShowDialog(); // Process open file dialog box results if (result == true) { ssortingng filename = dlg.FileName; // Invoke the command. MyViewModel myViewModel = (MyViewModel)DataContext; if (myViewModel .AttachFilesCommand.CanExecute(filename)) { noteViewModel.AttachFilesCommand.Execute(filename); } } } 

La programmation informatique est inflexible. Nous, les programmeurs, devons faire preuve de souplesse pour pouvoir traiter avec cela.

Le découplage est l’une des principales caractéristiques du MVVM. Si vous voulez modifier la vue ou le modèle lié. Dans quelle mesure est-ce facile pour votre application?

Prenons un exemple où View1 et View2 partagent tous deux le même ViewModel. Maintenant, vous allez implémenter la méthode du code derrière pour les deux.

En outre, supposez que si vous devez modifier le modèle de vue pour obtenir une vue à un stade ultérieur, votre commande échouera à mesure que le modèle de vue sera modifié et que l’instruction sera exécutée.

 MyViewModel vm = this.DataContext as MyViewModel; 

renverra null et donc le crash du code. Il y a donc un fardeau supplémentaire pour changer le code également. Ce genre de scénarios se produira si vous le faites de cette manière.

Bien sûr, il existe de nombreuses manières de réaliser la même chose dans la programmation, mais celle qui convient le mieux mènera à la meilleure approche.

La commande est pour les chumps. Les vrais hommes transmettent l’intégralité de leur interface aux événements en code.