Bons exemples de modèle MVVM


141

Je travaille actuellement avec le modèle Microsoft MVVM et je trouve le manque d'exemples détaillés frustrant. L'exemple ContactBook inclus montre très peu de gestion des commandes et le seul autre exemple que j'ai trouvé provient d'un article de MSDN Magazine où les concepts sont similaires mais utilisent une approche légèrement différente et manquent encore de complexité. Existe-t-il des exemples MVVM décents qui montrent au moins les opérations CRUD de base et la commutation de dialogue / contenu?


Les suggestions de chacun ont été vraiment utiles et je vais commencer à compiler une liste de bonnes ressources

Cadres / modèles

Articles utiles

Screencasts

Bibliothèques supplémentaires


Je suis heureux que ces ressources aient aidé. Je suis actuellement sur ma deuxième application MVVM de production et je continuerai à ajouter du contenu qui sera utile pour ceux qui débutent lorsque je la rencontre.
jwarzech

Réponses:


59

Malheureusement, il n'y a pas un seul excellent exemple d'application MVVM qui fasse tout, et il existe de nombreuses approches différentes pour faire les choses. Tout d'abord, vous voudrez peut-être vous familiariser avec l'un des frameworks d'application (Prism est un choix décent), car ils vous fournissent des outils pratiques tels que l'injection de dépendances, la commande, l'agrégation d'événements, etc. pour essayer facilement différents modèles qui vous conviennent. .

La version prisme:
http://www.codeplex.com/CompositeWPF

Il comprend un exemple d'application assez décent (le négociant en actions) ainsi que de nombreux exemples plus petits et comment faire. À tout le moins, c'est une bonne démonstration de plusieurs sous-modèles courants que les gens utilisent pour faire fonctionner MVVM. Ils ont des exemples de CRUD et de dialogues, je crois.

Prism n'est pas nécessairement pour tous les projets, mais c'est une bonne chose à connaître.

CRUD: Cette partie est assez simple, les liaisons bidirectionnelles WPF facilitent la modification de la plupart des données. Le vrai truc est de fournir un modèle qui facilite la configuration de l'interface utilisateur. À tout le moins, vous voulez vous assurer que votre ViewModel (ou objet métier) est implémenté INotifyPropertyChangedpour prendre en charge la liaison et que vous pouvez lier les propriétés directement aux contrôles de l'interface utilisateur, mais vous pouvez également implémenter IDataErrorInfopour la validation. En règle générale, si vous utilisez une sorte de solution ORM, la configuration de CRUD est un jeu d'enfant.

Cet article présente des opérations simples de crud: http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

Il est construit sur LinqToSql, mais cela n'est pas pertinent pour l'exemple - tout ce qui est important est que vos objets métier implémentent INotifyPropertyChanged(ce que font les classes générées par LinqToSql). MVVM n'est pas le point de cet exemple, mais je ne pense pas que cela compte dans ce cas.

Cet article illustre la validation des données
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

Encore une fois, la plupart des solutions ORM génèrent des classes qui implémentent déjà IDataErrorInfoet fournissent généralement un mécanisme pour faciliter l'ajout de règles de validation personnalisées.

La plupart du temps, vous pouvez prendre un objet (modèle) créé par un ORM et l'envelopper dans un ViewModel qui le contient et des commandes pour enregistrer / supprimer - et vous êtes prêt à lier l'interface utilisateur directement aux propriétés du modèle.

La vue ressemblerait à quelque chose comme ceci (ViewModel a une propriété Itemqui contient le modèle, comme une classe créée dans l'ORM):

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

Dialogues: les dialogues et MVVM sont un peu compliqués. Je préfère utiliser une saveur de l'approche Mediator avec des dialogues, vous pouvez en savoir un peu plus à ce sujet dans cette question StackOverflow:
Exemple de dialogue WPF MVVM

Mon approche habituelle, qui n'est pas tout à fait classique MVVM, peut se résumer comme suit:

Une classe de base pour un ViewModel de dialogue qui expose des commandes pour les actions de validation et d'annulation, un événement pour informer la vue qu'une boîte de dialogue est prête à être fermée, et tout ce dont vous aurez besoin dans toutes vos boîtes de dialogue.

Une vue générique pour votre boîte de dialogue - cela peut être une fenêtre ou un contrôle de type de superposition "modal" personnalisé. En son cœur, c'est un présentateur de contenu dans lequel nous vidons le viewmodel, et il gère le câblage pour fermer la fenêtre - par exemple, lors du changement de contexte de données, vous pouvez vérifier si le nouveau ViewModel est hérité de votre classe de base, et si c'est le cas, abonnez-vous à l'événement de fermeture pertinent (le gestionnaire attribuera le résultat de la boîte de dialogue). Si vous fournissez une autre fonctionnalité de fermeture universelle (le bouton X, par exemple), vous devez également vous assurer d'exécuter la commande de fermeture appropriée sur le ViewModel.

Quelque part, vous devez fournir des modèles de données pour vos ViewModels, ils peuvent être très simples, d'autant plus que vous avez probablement une vue pour chaque boîte de dialogue encapsulée dans un contrôle séparé. Le modèle de données par défaut pour un ViewModel ressemblerait alors à ceci:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

La vue de la boîte de dialogue doit y avoir accès, car sinon elle ne saura pas comment afficher le ViewModel, à part l'interface utilisateur de la boîte de dialogue partagée, son contenu est essentiellement le suivant:

<ContentControl Content="{Binding}" />

Le modèle de données implicite mappera la vue sur le modèle, mais qui le lance?

C'est la partie pas si mvvm. Une façon de le faire est d'utiliser un événement global. Ce que je pense qu'il vaut mieux faire est d'utiliser une configuration de type d'agrégateur d'événements, fournie par injection de dépendances - de cette façon, l'événement est global à un conteneur, pas à l'ensemble de l'application. Prism utilise le cadre d'unité pour la sémantique des conteneurs et l'injection de dépendances, et dans l'ensemble, j'aime beaucoup Unity.

Habituellement, il est logique que la fenêtre racine s'abonne à cet événement - elle peut ouvrir la boîte de dialogue et définir son contexte de données sur le ViewModel qui est transmis avec un événement déclenché.

Configurer cela de cette manière permet à ViewModels de demander à l'application d'ouvrir une boîte de dialogue et de répondre aux actions de l'utilisateur sans rien savoir sur l'interface utilisateur, de sorte que la MVVM-ness reste pour la plupart complète.

Il y a des moments, cependant, où l'interface utilisateur doit augmenter les dialogues, ce qui peut rendre les choses un peu plus délicates. Considérez par exemple si la position de la boîte de dialogue dépend de l'emplacement du bouton qui l'ouvre. Dans ce cas, vous devez disposer d'informations spécifiques à l'interface utilisateur lorsque vous demandez l'ouverture d'une boîte de dialogue. Je crée généralement une classe distincte qui contient un ViewModel et des informations d'interface utilisateur pertinentes. Malheureusement, un certain couplage y paraît inévitable.

Pseudo code d'un gestionnaire de bouton qui déclenche une boîte de dialogue nécessitant des données de position d'élément:

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

La vue de boîte de dialogue se liera aux données de position et transmettra le ViewModel contenu à l'intérieur ContentControl. Le ViewModel lui-même ne sait toujours rien de l'interface utilisateur.

En général, je n'utilise pas la DialogResultpropriété return de la ShowDialog()méthode ou je m'attends à ce que le thread se bloque jusqu'à ce que la boîte de dialogue soit fermée. Une boîte de dialogue modale non standard ne fonctionne pas toujours comme ça, et dans un environnement composite, vous ne voulez souvent pas vraiment qu'un gestionnaire d'événements se bloque comme ça de toute façon. Je préfère laisser les ViewModels s'occuper de cela - le créateur d'un ViewModel peut s'abonner à ses événements pertinents, définir des méthodes de validation / d'annulation, etc., il n'est donc pas nécessaire de s'appuyer sur ce mécanisme d'interface utilisateur.

Donc, au lieu de ce flux:

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

J'utilise:

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

Je le préfère ainsi car la plupart de mes dialogues sont des contrôles pseudo-modaux non bloquants et le faire de cette façon semble plus simple que de contourner cela. Test unitaire facile également.


Merci pour la réponse détaillée! J'ai récemment découvert que mon plus gros problème est lorsque je dois faire communiquer un MainViewModel avec d'autres modèles de vue pour gérer le flux de l'application. Cependant, il semble que MVVM + Mediator semble être l'approche la plus populaire.
jwarzech

2
Le Mediator est vraiment utile, le modèle d'agrégateur d'événements (Prism a une bonne implémentation) est également très utile lorsqu'un faible couplage est un objectif. En outre, votre modèle de vue principal a généralement ses propres modèles de vue enfants et ne devrait pas avoir de problèmes pour communiquer avec eux. Vous devez utiliser un médiateur ou / et un agrégateur d'événements lorsque vos viewmodels enfants doivent interagir avec d'autres modules de votre application qu'ils ne connaissent pas nécessairement - y compris l'interface utilisateur (mon exemple de dialogue concerne ce cas particulier).
Egor

1
Les instructions pour travailler avec les boîtes de dialogue et les fenêtres ont été très utiles. Cependant, je suis coincé avec quelques problèmes: 1. Comment définir le titre de la fenêtre à partir de la vue? 2. Comment gérez-vous la configuration de la fenêtre propriétaire?
djskinner le

@Daniel Skinner: Je suppose que vous parlez de dialogues ici, corrigez-moi si je me trompe. Le titre de la boîte de dialogue n'est qu'une autre propriété et vous pouvez le lier à ce que vous voulez. Si vous avez suivi mon approche avec une classe viewmodel de boîte de dialogue de base (supposons qu'elle a une propriété title), alors dans votre fenêtre de dialogue tout générique, vous pouvez utiliser la liaison UI à UI pour définir le titre sur {Binding Path = DataContext.Title, ElementName = NameOfContentPresenter}. La fenêtre du propriétaire est un peu plus compliquée - cela signifie que le médiateur qui ouvre réellement la boîte de dialogue doit connaître la vue racine de l'application.
Egor

En fait, je reprends cela - quelle que soit la façon dont vous structurez cela à un moment donné, quiconque ouvre réellement la boîte de dialogue doit avoir une référence à la fenêtre / vue de l'application racine. Remarquez où j'ai dit "Habituellement, il est logique que la fenêtre racine s'abonne à cet événement - elle peut ouvrir la boîte de dialogue et définir son contexte de données sur le modèle de vue qui est transmis avec un événement déclenché." C'est là que vous définiriez le propriétaire.
Egor

6

Jason Dolinger a fait un bon screencast de MVVM. Comme Egor l'a mentionné, il n'y a pas un seul bon exemple. Ils sont partout. La plupart sont de bons exemples MVVM, mais pas lorsque vous rencontrez des problèmes complexes. Chacun a sa manière. Laurent Bugnion a également un bon moyen de communiquer entre les viewmodels. http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx Cinch est également un bon exemple. Paul Stovel a un bon article qui en explique aussi beaucoup avec son framework Magellan.


3

Avez-vous regardé Caliburn ? L'exemple ContactManager contient beaucoup de bonnes choses. Les exemples WPF génériques fournissent également un bon aperçu des commandes. La documentation est assez bonne et les forums sont actifs. Conseillé!




2

L'exemple de projet dans le framework Cinch montre les outils de base CRUD et de navigation. C'est un assez bon exemple d'utilisation de MVVM, et comprend un article en plusieurs parties expliquant son utilisation et ses motivations.


2

J'ai également partagé votre frustration. J'écris une candidature et j'avais ces 3 exigences:

  • Extensible
  • WPF avec MVVM
  • Exemples compatibles GPL

Tout ce que j'ai trouvé était des fragments, alors j'ai commencé à l'écrire du mieux que je pouvais. Une fois que j'y suis entré un peu, j'ai réalisé qu'il pourrait y avoir d'autres personnes (comme vous) qui pourraient utiliser une application de référence, alors j'ai refactoré le contenu générique dans un cadre d'application WPF / MVVM et l'ai publié sous la LGPL. Je l'ai nommé SoapBox Core . Si vous accédez à la page de téléchargement, vous verrez qu'elle est livrée avec une petite application de démonstration, et le code source de cette application de démonstration est également disponible en téléchargement. J'espère que vous trouverez cela utile. Aussi, écrivez-moi à scott {at} soapboxautomation.com si vous voulez plus d'informations.

EDIT : a également publié un article CodeProject expliquant son fonctionnement.



1

Même moi, j'ai partagé la frustration jusqu'à ce que je prenne l'affaire en main. J'ai lancé IncEditor.

IncEditor ( http://inceditor.codeplex.com ) est un éditeur qui tente de présenter aux développeurs WPF, MVVM et MEF. Je l'ai commencé et j'ai réussi à obtenir des fonctionnalités telles que la prise en charge du «thème». Je ne suis pas expert en WPF, MVVM ou MEF, donc je ne peux pas y mettre beaucoup de fonctionnalités. Je vous demande sincèrement de l'améliorer afin que les fous comme moi puissent mieux le comprendre.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.