Je reçois mes culottes dans une touche récemment sur Afficher les modèles (VM). P>
Tout comme Ce gars est arrivé à la conclusion que les collections que je dois exposer sur mon VM contient généralement un type différent aux collections exposées sur mes objets métier. P>
Par conséquent, il doit y avoir une cartographie bidirectionnelle ou une transformation entre ces deux types. (Juste pour compliquer les choses, sur mon projet, ces données sont "vivantes" telles que dès que vous modifiez une propriété, elle est transmise à d'autres ordinateurs) p>
Je peux simplement faire face à ce concept, à l'aide d'un cadre comme TRUS , bien que je soupçonne qu'il y aura une mauvaise surprise quelque part à l'intérieur. P>
Non seulement les objets doivent être transformés, mais une synchronisation entre ces deux collections est requise. (Juste pour compliquer les choses que je peux penser aux cas où la collection VM pourrait être un sous-ensemble ou un synchronisation des collections d'objets métier, pas simplement une synchronisation 1: 1). P>
Je peux voir comment faire une synchronisation "en direct" à sens unique, à l'aide d'une observation de réplication de l'observaCollection ou de quelque chose comme Clinq. P>
Le problème devient alors: Quel est le meilleur moyen de créer / supprimer des articles? P>
La synchronisation bi-directive ne semble pas être sur les cartes - je n'ai trouvé aucun exemple de ce type et la seule classe qui prend en charge tout ce qui est à distance comme celle-ci est la liste ListCollectionview. La synchronisation bidirectionnelle serait-elle même un moyen sensible d'ajouter à la collection d'objets métier? P>
Tous les échantillons que j'ai vus ne semblent jamais aborder ce "complexe". P>
Donc, ma question est la suivante: comment résolvez-vous cela? Y a-t-il une technique pour mettre à jour les collections de modèle de la machine virtuelle? Quelle est la meilleure approche générale à cela? P>
6 Réponses :
Personnellement, j'utilise une observablecollection dans mon modèle et ma viewModel.
class Model
{
public ObservableCollection<Foo> Foos;
}
class ViewModel
{
public Model Model;
public ObservableCollection<FooView> Foos;
public ViewModel()
{
Model.Foos.CollectionChanged += OnModelFoosCollection_CollectionChanged;
}
void OnModelFoosCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Foo re;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
re = e.NewItems[0] as Foo;
if (re != null)
AddFoo(re); //For other logic that may need to be applied
break;
case NotifyCollectionChangedAction.Remove:
re = e.OldItems[0] as Foo;
if (re != null)
RemoveFoo(re);
break;
case NotifyCollectionChangedAction.Reset:
Foos.Clear();
/* I have an AddRange in an ObservableCollection-derived class
You could do Model.Foo.ForEach(ree => AddFoo(ree));
*/
var converter =
from ree in Model.Foo
select new FooView(ree);
Reports.AddRange(converter);
break;
default:
//exercise for the reader :)
s_ILog.Error("OnModelFoosCollection_CollectionChangedDid not deal with " + e.Action.ToString());
break;
}
}
void AddFoo(Foo f)
{
Foos.Add(new FooView(f));
}
void RemoveFoo(Foo f)
{
var match = from f in Foos
where f.Model == f //if you have a unique id, that might be a faster comparison
select f;
if(match.Any())
Foos.Remove(match.First());
}
}
Ok cela a du sens et était la conclusion que je suis venu à. Je veux toujours voir plus de preuves des approches d'autres peuples pour me donner confiance en ce qui est "le meilleur moyen". Par exemple, je n'ai jamais vu cette approche dans un poteau d'échantillon ou de blog: - /
Et si le modèle expose seulement un ilist ou quelque chose? Tourner chaque liste dans votre programme en une observablecollection n'est pas une solution. Et si votre modèle ne met pas en œuvre inotifyPropertychangned? Écoute d'une observablecollection dans la vue, puis repoussez les modifications du modèle fonctionne bien, mais vous ne pouvez pas vous attendre à ce que le modèle vous propose de telles commodités et que, dans de nombreux cas, l'observablecollection peut ne pas fournir les fonctionnalités dont vous avez besoin dans votre modèle.
J'ai tendance à faire plus de la Sacha Barber Style MVVM. J'écris un nouveau code, je n'ai pas de modèles hérités, alors j'ai rarement un modèle du tout. En général, si je veux de l'INPC, je l'écris. Si j'ai besoin d'observablecolection, je l'utilise.
La seule situation, lorsque vous pourriez avoir besoin de la synchronisation à double sens, c'est lorsque le contrôle que vous utilisez pour visualiser votre collection de VMS ne vous permet pas de connaître l'intention de l'utilisateur de créer ou de supprimer un élément. C'est à dire. Le contrôle traite directement avec votre collection de VMS et la seule façon de connaître que l'article a été ajouté / supprimé, consiste à surveiller la collection de VMS. Si ce n'est pas le cas, vous pouvez mettre en œuvre un moyen de synchroniser et d'ajouter / supprimer des éléments directement sur la collection du modèle.
Edit: Prenons par exemple WPF DataGrid Contrôle lié à la collecte observable de itemViewModels. Si c'est La propriété CanaserAddRows est définie sur True et l'utilisateur commence à taper dans la ligne vide en bas, le datagrid utilisera le constructeur par défaut de votre ItemViewModel pour créer un article en vrac et ensuite l'ajoutera à la collection. Il n'y a aucune indication de la DG qu'il veut ajouter un article la collection.c Je ne peux penser à aucun autre contrôle compliqué assez pour pouvoir ajouter des objets à collection seul.
L'opposé Scénario est quand vous avez ListView lié à votre collection et une commande qui indique l'intention de l'utilisateur de Ajouter un nouvel article - puis dans le gestionnaire de commandement Vous ajoutez simplement un nouvel article à Datamodel et laissez la synchronisation à sens unique faire le reste de le travail. Dans ce cas, ListView n'est pas capable d'ajouter à la collection il présente. p> blockquote>sur le processus de synchronisation elle-même, consultez Projet Linq liant - Il peut minimiser le code de quantité et améliorer la lisibilité. Par exemple, le code Tom posté se traduira par quelque chose comme ceci: p>
xxx pré> EDIT 2: Après avoir utilisé B-LINQ pendant un certain temps maintenant, je dois dire que vous pourriez avoir des problèmes de performance avec elle. Je l'ai utilisé pour synchroniser des collections relativement grandes (centaines d'éléments) de collections avec des dizaines d'éléments ajoutés et supprimés chaque seconde et je devais l'abandonner et mettre en œuvre la synchronisation de la façon dont Tom avait suggéré.
Je l'utilise encore B-LINQ bien que dans les parties du projet où les collections sont petites et les performances ne sont pas un problème. P> blockQuote> p>
Pouvez-vous donner des exemples de ces contrôles qui font / ne pas en informer?
Moi aussi je me débats avec la synchronisation bidirectionnelle de deux collections à utiliser avec WPF via MVVM. J'ai blogué MVVM: Envelopper ou ne pas envelopper? Combien devriez-vous envelopper le modèle? (Partie 1) et MVVM: envelopper ou ne pas envelopper? Devrait-il aussi envelopper les collections d'envelopper? (Partie 2) concernant la question, y compris un exemple de code qui montre une synchronisation à deux sens. Cependant, comme indiqué dans les postes, la mise en œuvre n'est pas idéale. Je le qualifierais comme une preuve de concept. P>
J'aime le blinq , clinq et obtus frameworks que Alex_p posté à propos. Ce sont un très bon moyen d'obtenir un côté de la synchronisation Behvaior. Peut-être que l'autre côté (de VM à Model) peut être mis en œuvre via un autre chemin? Je viens de poster Partie 3 sur mon blog qui discute d'une partie de cela. p>
D'après ce que je peux voir, la bidirectionnelle via Blinq et Clinq n'est pas prise en charge dans les cas où la relève LINQ projette les données à une nouvelle structure. p>
Cependant, il ressemble à la clinq que la clinique peut prendre en charge la synchronisation bidirectionnelle dans les cas où la requête LINQ renvoie le même type de données que la collection sous-jacente. Il s'agit davantage d'un scénario de filtrage, qui ne correspond pas au cas d'utilisation d'un mode de vue en enveloppant les données du modèle. P>
Heureux de voir que je ne suis pas la seule personne qui se lance avec ce problème :) J'arrive à la conclusion que les collections VM devraient être réadonnées, puis utilisez un autre mécanisme tel que la commande pour ajouter / supprimer. Je n'ai pas encore poc cependant
J'ai écrit des classes d'assistance pour emballer des collections observables d'objets métier dans leurs homologues de modèle ici , peut-être qu'il devrait être étendu pour aller dans l'autre sens. Toujours à la recherche de contributions ... P>
J'ai proposé un cadre général Annuler / Refaire basé sur MVVM, qui utilise certaines techniques liées au problème que vous décrivez. Il utilise des collections implémentant cette interface: (V est pour les éléments de mention, D pour les éléments de modèle) p> Utilisation de cette interface, il synchronise automatiquement la collection ViewModel lorsque le Modifications de la collecte de modèles. Si vous modifiez la viewModel, le changement est simplement redirigé vers la collection de modèles. P> La fonction GetViewItem vous donne une certaine flexibilité dans la manière dont les objets ViewModel sont liés à ses homologues de modèle. P> Vous pouvez Trouver les détails ici . P> ( J'avoue que la construction est assez complexe et je serai très heureux d'écouter des suggestions). P> p>
avec mon Observablecomputations Bibliothèque Vous pouvez créer une observablecollection résultant d'une autre observationCollection et synchronisée avec ça. Vous faites des opérations de standart (ajouter, supprimer) sur la couleur source: la collection calculée reflète toutes les modifications:
Filtering<Order> expensiveOrders = orders.Filtering(o => o.Price > 25);
expensiveOrders.CollectionChanged += (sender, eventArgs) =>
{
// see the changes (add, remove, replace, move, reset) here
};
// Start the changing...
orders.Add(new Order(8, 30));
orders.RemoveAt(1);
orders[0].Price = 60;
orders[4].Price = 10;
orders.Move(5, 1);
orders[1] = new Order(10, 17);
Pourriez-vous donner un court exemple quelles collections se produisent où? Je pense que je comprends votre problème, mais pas complètement. Qu'envoyez-vous d'utiliser les collections de logique d'entreprise, mais utilisez un convertisseur lorsque vous les utilisez? Qu'envoyez-vous d'utiliser une collection d'objets proxy contenant les objets logiques d'entreprise?