MVVM dans WPF - Comment alerter ViewModel des changements dans Model… ou devrais-je?

112

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 INotifyPropertyChangedet 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, Deckmais aussi la BlackJackGameclasse 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 BlackJackGamej'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 IsBustdoivent ê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?

Dave
la source
3
L'interaction que vous décrivez ressemble à un mécanisme d'événement standard est tout ce dont vous avez besoin. Le modèle peut exposer un événement appelé OnBustet la machine virtuelle peut s'y abonner. Je suppose que vous pouvez également utiliser une approche de l'AIE.
code4life
Je serai honnête, si je devais créer une véritable `` application '' de blackjack, mes données seraient cachées derrière quelques couches de services / proxies et un niveau pédant de tests unitaires semblable à A + B = C. Ce serait le proxy / service qui informe des changements.
Meirion Hughes
1
Merci à tout le monde! Malheureusement, je ne peux choisir qu'une seule réponse. Je choisis Rachel en raison des conseils d'architecture supplémentaires et du nettoyage de la question initiale. Mais il y a eu beaucoup de bonnes réponses et je les apprécie. -Dave
Dave
2
FWIW: Après avoir lutté pendant plusieurs années avec les complexités de la maintenance à la fois de VM et M par concept de domaine, je crois maintenant qu'avoir les deux échoue DRY; la séparation nécessaire des préoccupations peut être effectuée plus facilement en ayant deux INTERFACES sur un seul objet - une "interface de domaine" et une "interface ViewModel". Cet objet peut être transmis à la fois à la logique métier et à la logique View, sans confusion ni absence de synchronisation. Cet objet est un «objet d'identité» - il représente uniquement l'entité. Le maintien de la séparation du code de domaine et du code de vue nécessite alors de meilleurs outils pour le faire au sein d'une classe.
ToolmakerSteve

Réponses:

61

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:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

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 EventAggregatorou MVVM Light Messenger.

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 à PlayerScoreHasChangedMessagepartir d'un objet et un autre objet peut s'abonner pour écouter ces types de messages et mettre à jour sa PlayerScoreproprié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:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

et vous auriez un objet ViewModel comme

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(Les objets ci-dessus devraient tous être mis en œuvre INotifyPropertyChanged, mais je l'ai laissé de côté pour plus de simplicité)

Rachel
la source
3
Plus généralement, toutes les logiques / règles métier sont-elles incluses dans le modèle? Où va toute la logique qui dit que vous pouvez prendre une carte jusqu'à 21 (mais le croupier reste à 17), que vous pouvez diviser les cartes, etc. une classe de contrôleur BlacJackGame dans le modèle. J'essaie toujours de comprendre cela et j'apprécierais des exemples / références. L'idée du blackjack pour un exemple a été soulevée à partir d'une classe iTunes sur la programmation iOS où la logique / règles métier est très certainement dans la classe de modèle d'un modèle MVC.
Dave
3
@Dave Oui, la 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.
Rachel
6
Merci Rachel pour toute l'aide. Je vais devoir faire des recherches un peu plus ou écrire une autre question; Je suis toujours confus quant à l'emplacement de la logique du jeu. Vous (et d'autres) préconisez de le placer dans le ViewModel, d'autres disent «logique métier» qui, dans mon cas, je suppose que les règles du jeu et l'état du jeu appartiennent au modèle (voir par exemple: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) et stackoverflow.com/questions/10964003/… ). Je reconnais que dans ce jeu simple, cela n'a probablement pas beaucoup d'importance. Mais ce serait bien à savoir. Thxs!
Dave
1
@Dave J'utilise peut-être le terme «logique métier» de manière incorrecte et le mélange avec la logique applicative. Pour citer l'article MSDN que vous avez lié "Pour maximiser les opportunités de réutilisation, les modèles ne doivent contenir aucun comportement ou logique d'application spécifique à un cas d'utilisation ou à une tâche utilisateur" et "En règle générale, le modèle de vue définit des commandes ou des actions pouvant être représentées dans l'interface utilisateur et que l'utilisateur peut appeler " . Donc, des choses comme a DrawCardCommand()seraient dans le ViewModel, mais je suppose que vous pourriez avoir un BlackjackGameModelobjet contenant une DrawCard()méthode que la commande a appelée si vous le vouliez
Rachel
2
Évitez les fuites de mémoire. Utilisez un modèle WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
24

Ré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:

  1. Viewmodel est créé et enveloppe le modèle
  2. Viewmodel s'abonne à l' PropertyChangedévénement du modèle
  3. Viewmodel est défini comme vue DataContext, les propriétés sont liées, etc.
  4. Afficher l'action des déclencheurs sur viewmodel
  5. Viewmodel appelle la méthode sur le modèle
  6. Le modèle se met à jour
  7. Viewmodel gère les modèles PropertyChangedet élève les siens PropertyChangeden réponse
  8. La vue reflète les changements dans ses liaisons, fermant la boucle de rétroaction

D'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 .

Jon
la source
Bonjour, la liste que vous avez faite est géniale. J'ai cependant un problème avec 7. et 8. En particulier: j'ai un ViewModel, qui n'implémente pas INotifyPropertyChanged. Il contient une liste d'enfants, qui contient une liste d'enfants elle-même (il est utilisé comme ViewModel pour un contrôle WPF Treeview). Comment faire pour que UserControl DataContext ViewModel «écoute» les modifications de propriété dans l'un des enfants (TreeviewItems)? Comment m'abonner exactement à tous les éléments enfants, qui implémentent INotifyPropertyChanged? Ou devrais-je poser une question distincte?
Igor
4

Vos choix:

  • Implémenter INotifyPropertyChanged
  • Événements
  • POCO avec manipulateur Proxy

Comme je le vois, INotifyPropertyChangedc'est une partie fondamentale de .Net. c'est à dire son dans System.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):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler 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 ).

Meirion Hughes
la source
4

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

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

et voici la classe elle-même:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}
Roemer
la source
1
Évitez les fuites de mémoire. Utilisez un modèle WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
1
@JJS - OTOH, considérez que le modèle d'événement faible est dangereux . Personnellement, je préfère risquer une fuite de mémoire si j'oublie de désenregistrer ( -= 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.
ToolmakerSteve
@ToolmakerSteve merci d'avoir ajouté un argument équilibré. Je suggère aux développeurs de faire ce qu'il y a de mieux pour eux, dans leur propre situation. N'adoptez pas aveuglément le code source d'Internet. Il existe d'autres modèles comme la messagerie intercomposant couramment utilisée EventAggregator / EventBus (qui sont également travaillées avec leurs propres risques)
JJS
2

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:

Fondamentalement, la façon dont j'aime expliquer cela est que votre logique métier et vos entités constituent le modèle. C'est ce que votre application spécifique utilise, mais pourrait être partagé entre de nombreuses applications.

La vue est la couche de présentation - tout ce qui concerne l'interfaçage direct avec l'utilisateur.

Le ViewModel est fondamentalement la "colle" spécifique à votre application qui relie les deux.

J'ai un joli diagramme ici qui montre comment ils s'interfacent:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

Dans votre cas - abordons certains des détails ...

Validation: Cela se présente généralement sous 2 formes. La validation liée à la saisie de l'utilisateur se produirait dans le ViewModel (principalement) et dans la vue (c'est-à-dire: la zone de texte "Numérique" empêchant la saisie de texte est gérée pour vous dans la vue, etc.). En tant que tel, la validation de l'entrée de l'utilisateur est généralement une préoccupation de VM. Cela étant dit, il y a souvent une deuxième «couche» de validation - c'est la validation que les données utilisées correspondent aux règles métier. Cela fait souvent partie du modèle lui-même - lorsque vous transmettez des données à votre modèle, cela peut provoquer des erreurs de validation. La VM devra ensuite remapper ces informations vers la vue.

Opérations "dans les coulisses sans vue, comme écrire dans la base de données, envoyer des e-mails, etc.": Cela fait vraiment partie des "Opérations spécifiques au domaine" de mon diagramme, et fait uniquement partie du modèle. C'est ce que vous essayez d'exposer via l'application. Le ViewModel agit comme un pont pour exposer ces informations, mais les opérations sont purement Model.

Opérations pour le ViewModel: Le ViewModel a besoin de plus qu'un simple INPC - il a également besoin de toute opération spécifique à votre application (pas à votre logique métier), comme l'enregistrement des préférences et de l'état de l'utilisateur, etc. Cela va varier l'application. par app., même lors de l'interfaçage du même "modèle".

Une bonne façon d'y penser - Supposons que vous souhaitiez créer 2 versions de votre système de commande. Le premier est dans WPF et le second est une interface Web.

La logique partagée qui traite les commandes elles-mêmes (envoi d'e-mails, entrée dans DB, etc.) est le modèle. Votre application expose ces opérations et données à l'utilisateur, mais le fait de deux manières.

Dans l'application WPF, l'interface utilisateur (avec laquelle le visualiseur interagit) est la «vue» - dans l'application Web, il s'agit essentiellement du code qui (au moins éventuellement) est transformé en javascript + html + css sur le client.

Le ViewModel est le reste de la "colle" qui est nécessaire pour adapter votre modèle (ces opérations liées à la commande) afin de le faire fonctionner avec la technologie / couche de vue spécifique que vous utilisez.

VoteCafé
la source
Peut-être qu'un simple exemple est un lecteur de musique. Vos modèles contiendraient les bibliothèques, le fichier audio actif et les codecs, la logique du lecteur et le code de traitement du signal numérique. Les modèles de vue contiendraient vos contrôles, visualisations et navigateur de bibliothèque. il y a beaucoup de logique d'interface utilisateur nécessaire pour afficher toutes ces informations et il serait bien de permettre à un programmeur de se concentrer sur la lecture de la musique tout en permettant à un autre programmeur de se concentrer sur la rendre l'interface utilisateur intuitive et amusante. View-model et model devraient permettre à ces deux programmeurs de se mettre d'accord sur un ensemble d'interfaces et de travailler séparément.
VoteCoffee
Un autre bon exemple est une page Web. La logique côté serveur est généralement équivalente à un modèle. La logique côté client est généralement équivalente à un modèle de vue. J'imagine facilement que la logique du jeu appartiendrait au serveur et ne serait pas confiée au client.
VoteCoffee
2

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.

Vladimir Dorokhov
la source
Merci beaucoup d'avoir fait référence à l'excellent article de Josh Smith et d'avoir couvert les événements faibles!
JJS
1

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.

Ibrahim Najjar
la source
1

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

HappyNomad
la source
1

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.

KolA
la source
0

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.

Purée
la source
Si vous traitez avec des données hiérarchiques, vous voudrez consulter la démo 2 de mon article MVVM .
HappyNomad
0

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,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

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

Art
la source