MVVM: modèle modifié, comment mettre à jour correctement ViewModel et View?

Cas

Disons que j'ai une classe Person, un PersonViewModel et un PersonView.

La mise à jour des propriétés de PersonView vers le modèle Person est assez simple. PersonViewModel contient un objet Person et possède des propriétés publiques auxquelles PersonView se lie afin de mettre à jour le modèle Person.

Cependant.

Imaginez que le modèle Person puisse être mis à jour par Service. Maintenant, le changement de propriété doit être communiqué au PersonViewModel, puis au PersonView.

Voici comment je le réparerais:

Pour chaque propriété sur le modèle Person Je déclencherais L'événement PropertyChanged. PersonViewModel s'abonne à L'événement PropertyChanged de Person. {[1] } déclencherait alors un autre PropertyChanged afin de mettre à jour le PersonView.

Ce qui me semble le moyen le plus évident, mais je voudrais jeter cette question là-bas dans l'espoir que quelqu'un me montre une jolie manière. Est-ce vraiment aussi simple ou Existe-t-il de meilleurs moyens de marquer le modèle comme modifié et de mettre à jour les propriétés respectives sur le ViewModel?

Ajouts

Le DataContext du PersonView est PersonViewModel. Person est rempli à partir de JSON et est mis à jour plusieurs fois au cours de sa durée de vie.

N'hésitez pas à suggérer des modifications architecturales pour mon cas particulier.

Réponse

J'ai marqué aqwert comme réponse à ma question puisqu'il m'a fourni une alternative à la solution que j'ai déjà proposée.

26
demandé sur ndsc 2012-04-26 01:23:13

2 réponses

Si la vue est directement liée au Modèle, tant que le service utilise la même instance, toute modification des propriétés du modèle sera proposée à la vue.

Cependant, si vous recréez un nouveau modèle dans le service, vous donnerez le nouveau modèle au viewmodel. Je m'attends à voir le modèle en tant que propriété sur le modèle de vue, donc lorsque vous définissez cette propriété, toutes les liaisons doivent être alertées du changement.

//in the ViewModel
public Person Model
{
   get { return _person; }
   set { _person = value; 
         RaisePropertyChanged("Model");  //<- this should tell the view to update
        }
}

Modifier:

Puisque vous déclarez qu'il y a des ViewModel logique, alors vous pouvez personnaliser les propriétés dans le ViewModel

 private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
 {
      if(e.PropertyName == "Prop1") RaisePropertyChanged("SpecicalProperty");
      ...
 }

  public string SpecicalProperty
  {
     get
     {
         reutrn Model.Prop1 + " some additional logic for the view"; 
     }
   }

DANS XAML

  <TextBlock Text="{Binding Model.PropertyDirect}" />  
  <TextBlock Text="{Binding SpecicalProperty}" />

De cette façon, seules les propriétés Model et ViewModel sont liées à la vue sans dupliquer les données.

Vous pouvez créer un assistant pour lier les modifications de propriété du modèle au modèle de vue ou utiliser un dictionnaire de mappage

 _mapping.Add("Prop1", new string[] { "SpecicalProperty", "SpecicalProperty2" });

Puis recherchez les propriétés à mettre à jour en obtenant la liste des propriétés

 private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
 {

      string[] props;
      if(_mapping.TryGetValue(e.PropertyName, out props))
      {
          foreach(var prop in props)
              RaisePropertyChanged(prop);
      } 
 }
7
répondu aqwert 2012-04-25 22:37:08

Lorsque la vue se lie directement au modèle, vous mélangez le code de L'interface utilisateur et le code de données. Le but de MVVM est de séparer ces deux domaines de code. C'est ce que le ViewModel est pour.

Le modèle de vue doit avoir ses propres propriétés auxquelles la vue peut se lier. Un exemple:

class PersonViewModel
{
    private Person OriginalModel { get; set; }

    public ValueViewModel<string> Name { get; set; }
    public ValueViewModel<int> Postcode { get; set; }

    protected void ReadFromModel(Person person)
    {
        OriginalModel = person;
        Name.Value = OriginalModel.Name;
        Postcode.Value = OriginalModel.Postcode;
    }

    protected Person WriteToModel()
    {
        OriginalModel.Name = Name.Value; //...
        return OriginalModel;
    }
}

L'utilisation D'un tel ViewModel-design sépare vraiment vos objets de données de votre code d'interface utilisateur. Lorsque la personne de classe change, l'interface utilisateur n'a pas besoin d'être adaptée, car le ViewModel les sépare.

Maintenant pour votre question. Comme vous pouvez le voir dans l'exemple ci-dessus, j'ai utilisé un générique ValueViewModel<T>. Cette classe implémente INotifyPropertyChanged (et d'autres choses). Mais lorsque vous recevez une nouvelle personne, il vous suffit d'appeler ReadFromModel(newPerson) sur votre ViewModel pour que l'interface utilisateur soit mise à jour, car les ValueViewModels auxquels la vue se lie informent l'interface utilisateur lorsque leur valeur change.

Voici un exemple extrêmement simplifié de la structure interne du ValueViewModel:

class ValueViewModel<T> : INotifyPropertyChanged
{
    private T _value;
    public T Value 
    {
        get { return _value;}
        set
        {
            _value = value;
            RaisePropertyChanged("Value");
        }
    }
}

C'est une approche que nous avons utilisée dans notre bibliothèque MVVM. Il a l' avantage qu'il oblige le développeur à séparer clairement son code des préoccupations des concepteurs. Et comme un effet secondaire, il génère une disposition de code standardisée dans toutes vos vues et ViewModels et améliore ainsi la qualité du code.

25
répondu PVitt 2013-09-12 09:56:11