web-dev-qa-db-fra.com

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

Cas

Disons que j'ai une classe Person, une PersonViewModel et une 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 le PersonView se lie afin de mettre à jour le modèle Person.

Toutefois.

Imaginez que le modèle Person peut être mis à jour par Service. La modification de propriété doit maintenant être communiquée au PersonViewModel puis au PersonView.

Voici comment je le corrigerais:

Pour chaque propriété du modèle Person, je déclencherais l'événement PropertyChanged. PersonViewModel s'abonne à l'événement PropertyChanged de Person. PersonViewModel lèverait alors un autre PropertyChanged afin de mettre à jour le PersonView.

Cela me semble le moyen le plus évident, mais je veux en quelque sorte poser cette question dans l'espoir que quelqu'un me montre une manière plus agréable. Est-ce vraiment aussi simple ou existe-t-il de meilleures façons de marquer le modèle comme modifié et de mettre à jour les propriétés respectives sur le ViewModel?

Ajouts

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

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

Répondre

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

27
ndsc

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

Cependant, si vous recréez un nouveau modèle dans le service, vous donnerez au modèle de vue le nouveau modèle. Je m'attends à voir le modèle comme une 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
        }
}

ÉDITER:

Puisque vous déclarez qu'il existe une logique ViewModel spécifique, vous pouvez personnaliser ces propriétés dans 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"; 
     }
   }

EN 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 devenir plus créatif en créant une aide 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
aqwert

Lorsque la vue se lie directement au modèle (ce qui est également le cas lorsque le ViewModel expose le 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 à cela que sert le ViewModel.

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'une telle conception ViewModel sépare vraiment vos objets de données de votre code d'interface utilisateur. Lorsque la structure de la classe Person est modifiée, l'interface utilisateur n'a pas besoin d'être adaptée en conséquence, car le ViewModel les sépare les uns des autres.

Passons maintenant à votre question. Comme vous pouvez le voir dans l'exemple ci-dessus, j'ai utilisé un ValueViewModel<T> Générique. Cette classe implémente INotifyPropertyChanged (et quelques autres trucs). Lorsque vous recevez une nouvelle instance Person, il vous suffit d'appeler ReadFromModel(newPerson) sur votre ViewModel pour mettre à jour l'interface utilisateur, car les ValueViewModels auxquels la vue se lie informeront 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");
        }
    }
}

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

28
PVitt