In the previous part we use the viewmodel pattern to made views where all the editions of properties of the “data” viewmodel objects could be undo/redoed.
But, what happens with lists? or, if we have that the datamodel objects are represented by (data) viewmodel objects… how we have to present a list of data objects?
In this article I will give a possible solution to this more complex situation and how it could work with undo/redo.
To answer this questions I will try to follow the preceding case. In that case we have that the viewmodel shows to the view his own version of the data objects and his properties. The view was binded to this properties, but when some “set” method was called, the viewmodel submitted an undoitem to the project that finally modify the data when executed. After that the viewmodel is notified of the change of the data object and throws an notify change to the view.
Now we need a list that shows a viewmodel version of the model data. To construct this list we will use an new class that I will call MirrorCollection.
This list gets his data from some list of model ojects and will redirect all the changes to that list, like in the previous case.
The MirrorCollection<V,D> class
These are the more important class requirements:
1) every one of his items must be the viewmodel version of the corresponding item form the datamodel list
2) modifications in the mirror lista are redirected to modifications in the data model list using an undoitem
3) any modification in the datamodel list is notified to the mirror list with an INotifyCollectionChanged event
Ok, so lets go to implement it!
The mirror list constructor takes as parameters the datamodel list<D>, the project class to send the undoitems, and some class that implement the following interface that has to allow constructing the viewmodel V items form de dataobjects D:
public interface MirrorCollectionConversor<V, D>
{
V GetViewItem(D modelItem, int index);
D GetModelItem(V viewItem, int index);
}
Usually the viewmodel class will implement that.
The mirror collection class construction is as follows
public MirroredList(IList<D> baseList,
MirrorCollectionConversor<V, D> mirrorItemConversor,
IProject proj)
{
if (baseList == null)
throw new ArgumentNullException("baseList");
this._MirrorItemConversor = mirrorItemConversor;
this._proj = proj;
this._BaseList = baseList;
ICollection collection = _BaseList as ICollection;
INotifyCollectionChanged changeable =
_BaseList as INotifyCollectionChanged;
if (changeable == null)
throw new ArgumentException("List must support "
+ "INotifyCollectionChanged", "baseList");
if (collection != null)
Monitor.Enter(collection.SyncRoot);
try
{
ResetList();
changeable.CollectionChanged += new NotifyCollectionChangedEventHandler(changeable_CollectionChanged);
}
finally
{
if (collection != null)
Monitor.Exit(collection.SyncRoot);
}
}
private void ResetList()
{
_MirrorList = new List<V>();
int count = 0;
foreach (D res in _BaseList)
{
V viewItem = _MirrorItemConversor.GetViewItem(res,count);
count++;
_MirrorList.Add(viewItem);
}
}
Now we can implement the Insert, Delete actions of our mirror list in the following, quite elegant way
public void IList<V>.Insert(int index, D modelItem)
{
if (_SubmitCollectionChangedCommand == null)
ThrowReadOnly();
NotifyCollectionChangedEventArgs info =
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, modelItem, index);
_SubmitCollectionChangedCommand(info);
}
public void IList<V>.RemoveAt(int index)
{
if (_SubmitCollectionChangedCommand == null)
ThrowReadOnly();
D modelItem = _MirrorItemConversor.GetModelItem(this[index], index);
NotifyCollectionChangedEventArgs info =
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, modelItem, index);
_SubmitCollectionChangedCommand(info);
}
_SubmitCollecionChangedCommand is a delegate implemented by the viewmodel object who owns the mirrored list in the following way:
public void SubmitCollectionChangedCommand(
NotifyCollectionChangedEventArgs info)
{
UIEditList uiEditList = new UIEditList();
uiEditList.DataObject = ModelObject;
uiEditList.Info = info;
uiEditList.Items = ModelObject.ModelList;
_proj.Submit(uiEditList);
}
And here is the internal implementation of de UIListChange .DoCommand
public bool DoCommand(NotifyCollectionChangedEventArgs infoComm)
{
switch (infoComm.Action)
{
case NotifyCollectionChangedAction.Add:
if (infoComm.OldItems != null)
throw new ArgumentException("Old items present in Add?!", "info");
if (infoComm.NewItems == null)
throw new ArgumentException("New items not present in Add?!", "info");
ItemsResource.Insert(infoComm.NewStartingIndex, infoComm.NewItems[0]);
.....
The only important point that we have not covered is how our mirror list will process the NotifyCollectionChangedEvent that it receives from his model list. But the implementation is clear it has to replication all the insertions and deletes in his list.
An interesting point here is that Silverlight2 has a reduced version of NotifyCollectionChangedEventArgs action types: it does not include moves, only Add, Remove, Replace and Reset. I think that this is the only change required between the WPF and the Silverlight implementation.
Conclusion
Well, this is a quite long post and I have not commented nothing about how you will use this, so les quickly see how a viewmodel object will construct a mirror list:
public MirrorList<SomeView, SomeResouce> SomeList
{
get
{
if ( _someList == null)
_someList = new MirrorList<SomeView, SomeResource>(modelObjet.list, this, _proy);
return _someList;
}
}
and that’s all! You don’t have to worry about undos and redos… just do your inserts and deletes in you mirror list and everything will be done. Of course, your view will be also updated if the model list is updated from any other source, for example if your server notifies you that someone else has modified your object!
Note: I will be very pleased to receive your opinions about this, and possible ways to get it better.
Note: the ideas for the mirrorcollection class are taken form here and here, althoug the problen that they focus is quite different: in short it is that WPF does not react to changes in the data binded objects if the change is made by a different thread than the UI thread. In those articles they show ways to implement lists based in other list that could be modified by others thread.
Update: you can find the source code here.
Fantastic. For my current project I don’t need undo; I’m just looking for a good way to expose a DataModel collection as a ViewModel collection (and your MirrorCollection, with the code from UIEditList merged in, looks like just what I need). But I definitely see possibilities for future projects using the undo as well. I love how your ViewModels work as a perfect place to plug in an undo stack.
Okay, now I’m running into some questions in trying to actually implement your MirrorCollection. You don’t show all the code, so I’m having to make some guesses.
You don’t show the declaration of the MirrorCollection class itself. What does MirrorCollection descend from, and what interfaces does it implement?
I know it implements IList (from the explicit implementations of Insert and RemoveAt that you showed). Are you implementing all of IList manually, or are you inheriting from something else? (I probably would have tried descending from Collection, but you’re not overriding InsertItem and RemoveItem, so it looks like you picked another class.)
Does MirrorCollection implement INotifyCollectionChanged? (I’m assuming it does, and when it gets a collection-change notification from the model collection, it passes it along to the view, but with viewmodels in its NotifyCollectionChangedEventArgs rather than the datamodels it got from the datamodel collection’s eventargs. Am I right?)
Does MirrorCollection implement INotifyPropertyChanged like ObservableCollection does, or were you able to make everything work with just INotifyCollectionChanged and IList?
Thanks for your interest!
You are right, there is a lot of code missing in my explanation… I wanted to present first the ideas to receive feed back. But until now nobody seemed to be interested!
I will try to clean my code so I can publish it or send it to you!
It turned out I’m better off binding directly to my model objects for the moment (I’m only displaying them, not editing them, and a view model was more complexity than I needed; I’ll just handle the transforms using IValueConverter). So there’s no hurry, but I would be interested in seeing your code.
I’m particularly curious to see how it works if someone wants to add a new ViewModel to the MirrorCollection. Do you allow that? Or do adds have to go through the underlying DataModel collection (via whichever ViewModel owns the MirrorCollection)?
[...] and both collections need to be kept in sync in the face of changes. It’s possible to make an observable collection that wraps and adapts another observable collection, but that’s an awful lot of [...]
I’m interested! Please publish your code.
Thanks
I have make a github project with the code. You can find it here:
http://github.com/danice/MVVMUndoRedo/tree/master
Thanks for this great two-part series! This is very useful stuff. :)
I have created another command as follows:
public class BumpCommand : CommandModel
{
private VMAgenda _ViewModel;
public BumpCommand(VMAgenda aViewModel)
: base(“Bump”)
{
this._ViewModel = aViewModel;
}
public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _ViewModel._personsLists.Count > 0;
e.Handled = true;
}
public override void OnExecute(object sender, ExecutedRoutedEventArgs e)
{
foreach (var person in _ViewModel._personsLists)
person.Age++;
}
}
oh, I also added the following constructor to CommandModal:
public CommandModel(string name)
{
_routedCommand = new RoutedCommand(name, typeof(ApplicationCommands));
}
When I click the Bump command, it increments the ages in all the rows by one. This ends up generating three undo commands (for each of the three property changes). Two ways to handle this come to mind.
1) Some sort of BeginUndo, EndUndo that creates an undo command that is a collection of undo commands.
2) Create a “custom” undo command that would decrement the ages of all the rows.
I think both of these ideas would be worthwhile additions and will probably try and implement them. Any suggestions, or thoughts, on how to do this nicely within your framework before I get started?
Thanks!
- Brian
I’m also experiencing a strange WPF problem with the code. If I start up the program and delete the first person, the selected person is null and the name and age clear out. But, if I start up the program and delete the second person… the selected person is not null and the name and age do not clear out. Any idea about this?… I’m new to WPF and am just beginning to learn the odds and ends of things like Binding, WPF ListBox’es, etc… :|
First of all, thanks for you interest! Reading your comment there is a point witch I would like to clarify (maybe that’s not necessary to you, but only to be sure…)
I differentiate between Commands and UndoItems. Undo items are objects which encapsulate some action that can be undone. Any undoitem has the infomation that it needs to undo / redo himself. Also they are stored in a history list. So they are not commands. There is only one Undo command in the application who looks in the history list to call the first undoitem undo’s method.
So then, as you say, you have two options:
1. create a special reusable undoitem witch has a list of undoitems. To undo himself it calls all his undoitems undos in reverse order.
2. create a undoitem to make your special edition.
after that maybe you may also need a command to fire that undoitem.
I would make this comments to the first option:
A possible way to implement that is to have a property “currentUndoitem” in the project, of type “undoItemsList” or “composedUndoItem”. If it is assigned, when the project receives a submit, instead of adding the undoitem in the history it will add it to the undoItemsList.
When you are done with the your composite command you can clear the currentUndoItem and do the submit of the undoItemList to the project (who adds it to the history).
That’s a sort of “recording” state of the project. If you add persistence capabilities to your undoitems you could extend that to a sort of UI testing framework (for that you will need nested compositeUndoItems).
Also you will have to decide what would you do if one of your three editing commands give an error… will you undone all the command? (that could be related to two kinds of errors of the undoitems: warnings witch can continue the action, and errors witch cancel all).
To finish, it would be nice if you make a github project derived from the example (if only to practice with these promising git capabilities for managing projects)