Je suis en train de parcourir quelques articles MVVM, principalement ceci et cela .
Ma question spécifique est la suivante: comment communiquer les modifications du modèle du modèle au ViewModel?
Dans l'article de Josh, je ne vois pas qu'il fasse ça. Le ViewModel demande toujours au modèle des propriétés. Dans l'exemple de Rachel, elle a mis en œuvre le modèle INotifyPropertyChanged
et déclenche des événements à partir du modèle, mais ils sont destinés à être consommés par la vue elle-même (voir son article / code pour plus de détails sur la raison pour laquelle elle fait cela).
Nulle part je ne vois des exemples où le modèle alerte le ViewModel des modifications apportées aux propriétés du modèle. Cela me fait craindre que ce ne soit peut-être pas fait pour une raison quelconque. Existe-t-il un modèle pour alerter le ViewModel des changements dans le modèle? Cela semble nécessaire car (1) il y a probablement plus d'un ViewModel pour chaque modèle, et (2) même s'il n'y a qu'un seul ViewModel, une action sur le modèle peut entraîner la modification d'autres propriétés.
Je soupçonne qu'il pourrait y avoir des réponses / commentaires du type "Pourquoi voudriez-vous faire cela?" commentaires, voici donc une description de mon programme. Je suis nouveau sur MVVM alors peut-être que toute ma conception est défectueuse. Je vais le décrire brièvement.
Je programme quelque chose de plus intéressant (du moins pour moi!) Que les classes "Client" ou "Produit". Je programme BlackJack.
J'ai une vue qui n'a aucun code derrière et qui repose simplement sur la liaison aux propriétés et aux commandes dans le ViewModel (voir l'article de Josh Smith).
Pour le meilleur ou pour le pire, j'ai adopté l'attitude que le modèle devrait contenir non seulement des classes telles que PlayingCard
, Deck
mais aussi la BlackJackGame
classe qui maintient l'état de l'ensemble du jeu, et sait quand le joueur a fait faillite, le croupier doit piocher des cartes, et quel est le score actuel du joueur et du croupier (moins de 21, 21, buste, etc.).
De BlackJackGame
j'expose des méthodes comme "DrawCard" et il m'est venu à l'esprit que lorsqu'une carte est dessinée, des propriétés telles que CardScore
, et IsBust
doivent être mises à jour et ces nouvelles valeurs communiquées au ViewModel. C'est peut-être une pensée erronée?
On pourrait prendre l'attitude que le ViewModel a appelé la DrawCard()
méthode afin qu'il sache demander un score mis à jour et savoir s'il est en panne ou non. Des avis?
Dans mon ViewModel, j'ai la logique de saisir une image réelle d'une carte à jouer (en fonction de la couleur, du rang) et de la rendre disponible pour la vue. Le modèle ne devrait pas être concerné par cela (peut-être qu'un autre ViewModel utiliserait simplement des nombres au lieu d'images de cartes à jouer). Bien sûr, certains me diront peut-être que le modèle ne devrait même pas avoir le concept d'un jeu BlackJack et que cela devrait être géré dans le ViewModel?
OnBust
et la machine virtuelle peut s'y abonner. Je suppose que vous pouvez également utiliser une approche de l'AIE.Réponses:
Si vous souhaitez que vos modèles alertent les ViewModels des modifications, ils doivent implémenter INotifyPropertyChanged et les ViewModels doivent s'abonner pour recevoir des notifications PropertyChange.
Votre code pourrait ressembler à ceci:
Mais en général, cela n'est nécessaire que si plusieurs objets apportent des modifications aux données du modèle, ce qui n'est généralement pas le cas.
Si jamais vous avez un cas où vous n'avez pas réellement de référence à votre propriété Model pour y attacher l'événement PropertyChanged, vous pouvez utiliser un système de messagerie tel que Prism
EventAggregator
ou MVVM LightMessenger
.J'ai un bref aperçu des systèmes de messagerie sur mon blog, mais pour résumer, tout objet peut diffuser un message, et tout objet peut s'abonner pour écouter des messages spécifiques. Ainsi, vous pouvez diffuser un à
PlayerScoreHasChangedMessage
partir d'un objet et un autre objet peut s'abonner pour écouter ces types de messages et mettre à jour saPlayerScore
propriété lorsqu'il en entend un.Mais je ne pense pas que cela soit nécessaire pour le système que vous avez décrit.
Dans un monde MVVM idéal, votre application est composée de vos ViewModels, et vos modèles sont les seuls blocs utilisés pour créer votre application. Ils ne contiennent généralement que des données, donc n'auraient pas de méthodes telles que
DrawCard()
(qui serait dans un ViewModel)Vous auriez donc probablement des objets de données de modèle simples comme ceux-ci:
et vous auriez un objet ViewModel comme
(Les objets ci-dessus devraient tous être mis en œuvre
INotifyPropertyChanged
, mais je l'ai laissé de côté pour plus de simplicité)la source
DrawCard()
méthode serait dans le ViewModel, avec votre autre logique de jeu. Dans une application MVVM idéale, vous devriez être en mesure d'exécuter entièrement votre application sans l'interface utilisateur, simplement en créant des ViewModels et en exécutant leurs méthodes, par exemple via un script de test ou une fenêtre d'invite de commandes. Les modèles ne sont généralement que des modèles de données contenant des données brutes et une validation de données de base.DrawCardCommand()
seraient dans le ViewModel, mais je suppose que vous pourriez avoir unBlackjackGameModel
objet contenant uneDrawCard()
méthode que la commande a appelée si vous le vouliezRéponse courte: cela dépend des spécificités.
Dans votre exemple, les modèles sont mis à jour "seuls" et ces changements doivent bien sûr se propager aux vues. Étant donné que les vues peuvent uniquement accéder directement aux modèles de vue, cela signifie que le modèle doit communiquer ces modifications au modèle de vue correspondant. Le mécanisme établi pour le faire est bien sûr
INotifyPropertyChanged
, ce qui signifie que vous obtiendrez un flux de travail comme celui-ci:PropertyChanged
événement du modèleDataContext
, les propriétés sont liées, etc.PropertyChanged
et élève les siensPropertyChanged
en réponseD'un autre côté, si vos modèles contenaient peu (ou pas) de logique métier, ou si pour une autre raison (comme l'acquisition de capacités transactionnelles) vous décidiez de laisser chaque viewmodel "posséder" son modèle encapsulé, toutes les modifications apportées au modèle passeraient par le modèle de vue de sorte qu'un tel arrangement ne serait pas nécessaire.
Je décris une telle conception dans une autre question MVVM ici .
la source
Vos choix:
Comme je le vois,
INotifyPropertyChanged
c'est une partie fondamentale de .Net. c'est à dire son dansSystem.dll
. L'implémenter dans votre «modèle» s'apparente à l'implémentation d'une structure d'événement.Si vous voulez du POCO pur, vous devez effectivement manipuler vos objets via des proxies / services, puis votre ViewModel est informé des changements en écoutant le proxy.
Personnellement, je mets simplement en œuvre INotifyPropertyChanged de manière lâche, puis j'utilise FODY pour faire le sale boulot à ma place. Il ressemble et se sent POCO.
Un exemple (en utilisant FODY pour IL Weave les relanceurs PropertyChanged):
alors vous pouvez demander à votre ViewModel d'écouter PropertyChanged pour tout changement; ou des modifications spécifiques à la propriété.
La beauté de la route INotifyPropertyChanged est que vous la chaînez avec une ObservableCollection étendue . Donc, vous videz vos objets proches de poco dans une collection et écoutez la collection ... si quelque chose change, n'importe où, vous en apprenez davantage à ce sujet.
Je vais être honnête, cela pourrait rejoindre la discussion "Pourquoi INotifyPropertyChanged n'a-t-il pas été géré automatiquement par le compilateur", qui se résume à: Chaque objet dans c # devrait avoir la possibilité de notifier si une partie de celui-ci a été modifiée; c'est-à-dire implémenter INotifyPropertyChanged par défaut. Mais ce n'est pas le cas et la meilleure voie, qui nécessite le moins d'effort, est d'utiliser IL Weaving (en particulier FODY ).
la source
Sujet assez ancien mais après de nombreuses recherches, j'ai trouvé ma propre solution: A PropertyChangedProxy
Avec cette classe, vous pouvez facilement vous inscrire à NotifyPropertyChanged de quelqu'un d'autre et prendre les mesures appropriées s'il est déclenché pour la propriété enregistrée.
Voici un exemple de ce à quoi cela pourrait ressembler lorsque vous avez une propriété de modèle "Status" qui peut changer d'elle-même et qui doit alors automatiquement informer le ViewModel de déclencher sa propre propriété PropertyChanged sur sa propriété "Status" afin que la vue soit également notifiée: )
et voici la classe elle-même:
la source
-= my_event_handler
), car c'est plus facile à localiser qu'un problème de zombies rare + imprévisible qui peut-ou-ne-peut-jamais arriver.J'ai trouvé cet article utile: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf
Mon résumé:
L'idée derrière l'organisation MVVM est de permettre une réutilisation plus facile des vues et des modèles et également de permettre des tests découplés. Votre modèle de vue est un modèle qui représente les entités de vue, votre modèle représente les entités commerciales.
Et si vous vouliez faire une partie de poker plus tard? Une grande partie de l'interface utilisateur doit être réutilisable. Si votre logique de jeu est liée à votre modèle de vue, il serait très difficile de réutiliser ces éléments sans avoir à reprogrammer le modèle de vue. Et si vous souhaitez modifier votre interface utilisateur? Si votre logique de jeu est couplée à votre logique de modèle de vue, vous devrez revérifier que votre jeu fonctionne toujours. Et si vous souhaitez créer un bureau et une application Web? Si votre modèle de vue contient la logique du jeu, il deviendrait compliqué d'essayer de maintenir ces deux applications côte à côte car la logique de l'application serait inévitablement liée à la logique métier du modèle de vue.
Les notifications de modification des données et la validation des données se produisent dans chaque couche (la vue, le modèle de vue et le modèle).
Le modèle contient vos représentations de données (entités) et votre logique métier spécifiques à ces entités. Un jeu de cartes est une «chose» logique avec des propriétés inhérentes. Un bon deck ne peut pas contenir de cartes en double. Il doit exposer un moyen d'obtenir la (les) carte (s) supérieure (s). Il a besoin de savoir qu'il ne doit pas distribuer plus de cartes qu'il n'en reste. De tels comportements de deck font partie du modèle car ils sont inhérents à un deck de cartes. Il y aura également des modèles de croupiers, des modèles de joueurs, des modèles de mains, etc.
Le modèle de vue comprendrait la logique de présentation et d'application. Tout le travail associé à l'affichage du jeu est séparé de la logique du jeu. Cela peut inclure l'affichage des mains sous forme d'images, les demandes de cartes au modèle du revendeur, les paramètres d'affichage de l'utilisateur, etc.
Les tripes de l'article:
la source
La notification basée sur INotifyPropertyChanged et INotifyCollectionChanged est exactement ce dont vous avez besoin. Pour vous simplifier la vie avec l'abonnement aux modifications de propriété, la validation à la compilation du nom de la propriété, en évitant les fuites de mémoire, je vous conseillerais d'utiliser PropertyObserver de la MVVM Foundation de Josh Smith . Comme ce projet est open source, vous pouvez ajouter uniquement cette classe à votre projet à partir des sources.
Pour comprendre, comment utiliser PropertyObserver, lisez cet article .
Jetez également un œil plus profond sur les extensions réactives (Rx) . Vous pouvez exposer IObserver <T> à partir de votre modèle et vous y abonner dans le modèle de vue.
la source
Les gars ont fait un travail incroyable pour répondre à cela, mais dans des situations comme celle-ci, j'ai vraiment le sentiment que le modèle MVVM est pénible, alors j'irais utiliser un contrôleur de supervision ou une approche de vue passive et abandonner le système de liaison au moins pour les objets modèles qui génèrent des changements par eux-mêmes.
la source
Je préconise le modèle directionnel -> Afficher le modèle -> Afficher le flux des modifications depuis longtemps, comme vous pouvez le voir dans la section Flux des modifications de mon article MVVM de 2008. Cela nécessite une implémentation
INotifyPropertyChanged
sur le modèle. Pour autant que je sache, c'est devenu une pratique courante.Parce que vous avez mentionné Josh Smith, jetez un œil à sa classe PropertyChanged . C'est une classe d'aide pour s'abonner à l'
INotifyPropertyChanged.PropertyChanged
événement du modèle .Vous pouvez en fait aller beaucoup plus loin dans cette approche, comme je l'ai récemment fait en créant ma classe PropertiesUpdater . Les propriétés du modèle de vue sont calculées comme des expressions complexes qui incluent une ou plusieurs propriétés sur le modèle.
la source
Il n'y a rien de mal à implémenter INotifyPropertyChanged dans Model et à l'écouter dans ViewModel. En fait, vous pouvez même pointer dans la propriété du modèle directement en XAML: {Binding Model.ModelProperty}
En ce qui concerne les propriétés en lecture seule dépendantes / calculées, je n'ai de loin rien vu de mieux et de plus simple que cela: https://github.com/StephenCleary/CalculatedProperties . C'est très simple mais incroyablement utile, ce sont vraiment des «formules Excel pour MVVM» - fonctionne de la même manière qu'Excel propage les modifications aux cellules de formule sans effort supplémentaire de votre part.
la source
Vous pouvez déclencher des événements à partir du modèle, auxquels le modèle de vue devra s'abonner.
Par exemple, j'ai récemment travaillé sur un projet pour lequel je devais générer une arborescence (naturellement, le modèle avait un caractère hiérarchique). Dans le modèle, j'avais une collection d'observation appelée
ChildElements
.Dans le viewmodel, j'avais stocké une référence à l'objet dans le modèle, et souscrit à l'
CollectionChanged
événement de la collection observable, comme ceci:ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)
...Ensuite, votre viewmodel est automatiquement notifié une fois qu'un changement se produit dans le modèle. Vous pouvez suivre le même concept en utilisant
PropertyChanged
, mais vous devrez déclencher explicitement des événements de modification de propriété à partir de votre modèle pour que cela fonctionne.la source
Cela me semble être une question vraiment importante - même lorsqu'il n'y a aucune pression pour le faire. Je travaille sur un projet de test, qui implique un TreeView. Il existe des éléments de menu et autres qui sont mappés à des commandes, par exemple Supprimer. Actuellement, je mets à jour le modèle et le modèle de vue depuis le modèle de vue.
Par exemple,
C'est simple, mais semble avoir un défaut très basique. Un test unitaire typique exécuterait la commande, puis vérifierait le résultat dans le modèle de vue. Mais cela ne vérifie pas que la mise à jour du modèle était correcte, car les deux sont mis à jour simultanément.
Il est donc peut-être préférable d'utiliser des techniques comme PropertyObserver pour laisser la mise à jour du modèle déclencher une mise à jour du modèle de vue. Le même test unitaire ne fonctionnerait plus que si les deux actions réussissaient.
Ce n'est pas une réponse potentielle, je m'en rends compte, mais cela semble valoir la peine d'être présenté.
la source