Synchronizing a bidirectional view model with live collections and properties

Recently, I get my panties regarding view models (VM).

Like this guy , I came to the conclusion that the collections that I need to present on my virtual machine usually contain a different type for collections open on my business objects.

Therefore, there must be a bi-directional mapping or conversion between the two types. (To complicate the situation, in my project this data is “Live”, so as soon as you change the property, it will be transferred to other computers).

I can just handle this concept using a framework like Truss , although I suspect there will be an unpleasant surprise somewhere inside.

Not only objects need to be transformed, but synchronization between the two collections is required. (To complicate matters, I can think of cases where a VM collection can be a subset or a collection of collections of business objects, and not just 1: 1 synchronization).

I can see how to do one-way live synchronization using ObservableCollection replication or something like CLINQ.

Then the problem arises: what is the best way to create / delete items?

Bi-directinal sync does not seem to be present on maps - I have not found such examples, and the only class that supports anything like this is ListCollectionView. Would bidirectional synchronization be an even smart way to add back to a collection of business objects?

All the samples that I saw never seem to do this “complicated” thing.

So my question is: how do you solve this? Is there any technique for updating model collections from a virtual machine? What is the best overall approach to this?

+6
design-patterns wpf mvvm
source share
5 answers

I am also struggling with bidirectional synchronization of two collections for use with WPF via MVVM. I'm on the MVVM Blog : Wrap or Not Wrap? How much should ViewModel wrap the model? (Part 1) and MVVM: to wrap or not to wrap? Should ViewModels streamline collections? (Part 2) regarding the question, including sample code that shows two-way synchronization. However, as noted in the posts, the implementation is not perfect. I would qualify it as a proof of concept.

I like the BLINQ , CLINQ, and Obtics that Alex_P has reported. This is a very good way to get one side sync behvaior. Maybe the other side (from VM to the model) can be implemented through an alternative path? I just posted part 3 on my blog discussing some of these issues.

From what I see, bidirectional using BLINQ and CLINQ is not supported in cases where the LINQ statement passes data to a new structure.

However, it looks like CLINQ can support bi-directional synchronization in cases where the LINQ query returns the same data type as the base collection. It is rather a filtering scenario that does not match the use case of the ViewModel, which wraps data in the model.

+3
source share

Personally, I use ObservableCollection in my model and my view model.

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()); } } 

Now, when you delete something from the Model Foo collection, it automatically deletes the corresponding FooView. This is consistent with how I think about it. If I want to delete something, the model must be deleted.

This seems like a lot of code, but actually it is not. I am sure that it would be possible to create a general version of this, but IMO you always need custom logic related to adding / removing elements.

+4
source share

The only situation where you may need two-way synchronization is when the control that you use to visualize your collection of virtual machines does not let you know about the user’s intention to create or delete the item. That is, management is directly related to your collection of virtual machines and ONLY, as you know, that an item has been added / removed, it is carried out by monitoring the collection of virtual machines. If this is not the case, you can implement one-way synchronization and add / remove elements directly in the model collection.

EDIT: Take, for example, the WPF DataGrid control associated with the observed collection of ItemViewModels. If this CanUserAddRows property is set to true and the user begins to enter an empty line at the bottom, the DataGrid will use the default constructor for your ItemViewModel to create an unsecured item and then add it to the collection. There is no indication from DG that he wants to add an element to the collection .c I cannot think of any other complicated process enough to be able to add elements to the collection myself.
The opposite scenario is when you have a ListView associated with your collection and a command that indicates the user’s intention to add a new item - then in the command handler you simply add a new item to the DataModel and let one-way synchronization do the rest of the work. In this case, ListView is not an opportunity to add gifts to the collection.

Regarding the synchronization process itself, take a look at Bindable LINQ - it can minimize the sum code and improve readability. For example, the Tom code to be published will translate into the following:

 class ViewModel { public Model Model; public ObservableCollection<FooView> Foos; public ViewModel() { Foos = from foo in Model.Foos.AsBindable() select new FooView(foo); } } 

EDIT 2: after using B-LINQ for some time, I have to say that you may have performance issues. I used it to synchronize relatively large collections (hundreds of elements) with the addition and removal of dozens of elements each time, and I had to abandon it and implement synchronization, as Tom suggested.
I still use B-LINQ, although in those parts of the project where the collections are small and performance is not a problem.

+3
source share

I wrote some helper classes to wrap observable collections of business objects in their Model Model samples here , perhaps it should be expanded to Go the other way. always looking for contributions ...

+1
source share

I suggested a general MVDM-based Undo / Redo framework that uses some methods related to the problem you described. It uses collections that implement this interface:

 public interface MirrorCollectionConversor<V, D> { V GetViewItem(D modelItem, int index); D GetModelItem(V viewItem, int index); } 

(V for ViewModel elements, D for model elements)

Using this interface, it automatically synchronizes the viewmodel collection when the model collection changes. If you change the view, the change is simply redirected to the collection of models.

The GetViewItem function gives you some flexibility regarding how viewmodel objects are related to its model counterparts.

Here you can find the details.

(I admit that the design is quite complex, and I will be very happy to hear suggestions).

+1
source share

All Articles