2010-05-17 6 views
2

Dans le modèle, je:WPF Liste des ViewModels liés à la liste des objets Modèle

public ObservableCollection<Item> Items { get; private set; } 

Dans le ViewModel, j'ai une liste correspondante de ItemViewModels. Je voudrais que cette liste soit deux sens lié à la liste du modèle:

public ObservableCollection<ItemViewModel> ItemViewModels ... 

Dans le XAML, je lie (dans ce cas un TreeView) à la propriété ItemViewModels.

Ma question est, qu'est-ce qui se passe dans le "..." dans le ViewModel montré ci-dessus? J'espère qu'une ligne ou deux de code lie ces deux ObservableCollections (fournissant le type de ViewModel à construire pour chaque objet de modèle). Cependant, ce que je crains est nécessaire est un tas de code pour gérer l'événement Items.CollectionChanged et met à jour manuellement la liste ItemViewModels en construisant ViewModels si nécessaire, et le contraire qui mettra à jour la collection Items basée sur les modifications de ItemViewModels.

Merci!

Eric

Répondre

5

Vous pouvez utiliser la classe suivante:

public class BoundObservableCollection<T, TSource> : ObservableCollection<T> 
{ 
    private ObservableCollection<TSource> _source; 
    private Func<TSource, T> _converter; 
    private Func<T, TSource, bool> _isSameSource; 

    public BoundObservableCollection(
     ObservableCollection<TSource> source, 
     Func<TSource, T> converter, 
     Func<T, TSource, bool> isSameSource) 
     : base() 
    { 
     _source = source; 
     _converter = converter; 
     _isSameSource = isSameSource; 

     // Copy items 
     AddItems(_source); 

     // Subscribe to the source's CollectionChanged event 
     _source.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_source_CollectionChanged); 
    } 

    private void AddItems(IEnumerable<TSource> items) 
    { 
     foreach (var sourceItem in items) 
     { 
      Add(_converter(sourceItem)); 
     } 
    } 

    void _source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Add: 
       AddItems(e.NewItems.Cast<TSource>()); 
       break; 
      case NotifyCollectionChangedAction.Move: 
       // Not sure what to do here... 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       foreach (var sourceItem in e.OldItems.Cast<TSource>()) 
       { 
        var toRemove = this.First(item => _isSameSource(item, sourceItem)); 
        this.Remove(toRemove); 
       } 
       break; 
      case NotifyCollectionChangedAction.Replace: 
       for (int i = e.NewStartingIndex; i < e.NewItems.Count; i++) 
       { 
        this[i] = _converter((TSource)e.NewItems[i]); 
       } 
       break; 
      case NotifyCollectionChangedAction.Reset: 
       this.Clear(); 
       this.AddItems(_source); 
       break; 
      default: 
       break; 
     } 
    } 
} 

utiliser comme suit:

var models = new ObservableCollection<Model>(); 
var viewModels = 
    new BoundObservableCollection<ViewModel, Model>(
     models, 
     m => new ViewModel(m), // creates a ViewModel from a Model 
     (vm, m) => vm.Model.Equals(m)); // checks if the ViewModel corresponds to the specified model 

Le BoundObservableCollection sera mis à jour lorsque le ObservableCollection va changer, mais pas l'inverse (vous ne l'avez avoir à remplacer quelques méthodes pour le faire)

+0

Merci! Je savais que quelqu'un avait déjà fait le travail pour encapsuler cette fonctionnalité. Une question: Je vois que vous utilisez le isSameSource lorsque l'action de changement est Supprimer. Pourquoi ne pas simplement utiliser le e.OldStartingIndex et e.OldItems.Count: pour (int i = 0; i Eric

+0

@Eric, oui, ça pourrait marcher, et ce serait mieux ...si vous êtes certain que les deux collections sont dans le même ordre. Comme je ne gère pas le cas 'NotifyCollectionChangedAction.Move', ce n'est pas garanti ... –

+0

Ey merci. Juste ce que je cherchais :) –

2

Oui, vos craintes sont vraies, vous auriez à envelopper toutes les fonctionnalités ObservableCollection.

Ma question de retour est cependant, pourquoi voudriez-vous avoir autour de ce modèle qui semble déjà être beau modèle? Le modèle View est utile si votre modèle de données est basé sur une logique métier non programmable. Normalement, cette couche d'entreprise/de données a une ou deux façons de récupérer des données et de notifier les observateurs externes de leurs modifications qui sont facilement traitées par le modèle de vue et converties en modifications au ObservableCollection. En fait, dans .NET 3.5 ObservableCollection faisait partie de WindowsBase.dll, donc normalement il ne serait pas utilisé dans les modèles de données en premier lieu. Ma suggestion est soit la logique qui remplit/modifie ObservableCollection doit être déplacée de votre modèle de données dans le modèle de vue, ou vous devez simplement lier directement à la couche que vous appelez actuellement le modèle de données et l'appeler comme il est. Un modèle de vue.

Vous pouvez évidemment écrire une classe d'aide qui sera en cours de synchronisation deux collections utilisant des lambdas de conversion (Item-ItemViewModel et en arrière) et de l'utiliser partout dans des endroits comme celui-ci (assurez-vous de gérer l'unicité de l'élément bien que), mais à mon humble avis cette approche génère une quantité redondante de classes wrapper, et chaque couche réduit la fonctionnalité et ajoute de la complexité. Ce qui est exactement le contraire des objectifs du MVVM.

Questions connexes