We have already discussed how to handle change tracking in collections and in complex models and we have introduced how to handle change tracking in a MVVM based model.
We want to start where we left adding a collection to the Person class and setup the entire editing pipeline for the collection too.
If we look at the considerations we did for the simple view model is obvious that the Address class itself needs a ViewModel and an editor and also the collection exposed by the Person class needs an editor and potentially a ViewModel depending on the type of editing that we want to support.
We need to face a couple more issues related to the fact that having one graph coming from a persistent storage and one different graph bound to the UI we need to keep them in sync.
The AddressViewModel will be as simple as the PersonViewModel we already saw:
classAddressViewModel:MementoEntity{publicvoidInitialize( Address address,Boolean registerAsTransient ) {if( registerAsTransient ) {this.RegisterTransient(); }this.SetInitialPropertyValue( () =>this.Street,address.With( a =>a.Street ).Return( s => s,"" ) );this.SetInitialPropertyValue( () =>this.City,address.With( a =>a.City ).Return( c => c,"" ) ); }publicString Street {get { returnthis.GetPropertyValue( () =>this.Street ); }set { this.SetPropertyValue( () =>this.Street, value ); } }publicString City {get { returnthis.GetPropertyValue( () =>this.City ); }set { this.SetPropertyValue( () =>this.City, value ); } }}
Nothing new, except for the With/Return syntax that is simply a monad like way to guard against null adding a default value.
Things get much more interesting as we look at the PersonViewModel, that revisited, now handle the Addresses list:
We are using a MementoEntityCollection<T> to keep track of changes that occurs to the collection structure, such as add or address removal, we are using the BulkLoad API to achieve 2 goals:
Add a transformation on load, we are basically iterating over Address instances adding to the collection AddressViewModel instances, and the transformation is done in the delegate via the CreateAddressViewModel that simply wraps the Address instance, if any, into the AddressViewModel instance initializing it as we saw for the Person / PersonViewModel relationship;
disable at once collection notifications, a IEntityCollection<T> has built-in support for changes notification, and a MementoEntityCollection<T> for change tracking, the BulkLoad API will disable notifications and tracking for the entire load process re-enabling both at the end;
We then expose our Addresses list as an IEntityView, that is an IBindingListView implementation, achieving 2 goals:
In the View we can now bind the collection to a DataGrid, for example, gaining full support for sorting, filtering and column generation;
We can have control, very easily, over new items generation even if the request is done by a DataGrid control: simply add a EventHandler to the AddingNew event of the IEntityView and create the expected instance;
The last thing to do is to manually propagate the current ChangeTrackingService instance to the collection owned by the PersonViewModel class, we do that overriding the OnMementoChanged method that is called every time the current memento tracking this instance changes.
The last thing is to update the EditorViewModel to create a sample data set; we also add a couple of commands to manage the Addresses collection and a property to keep track of the currently selected address:
classEditorViewModel:AbstractViewModel{readonlyIChangeTrackingService service =newChangeTrackingService();publicEditorViewModel() {var observer =MementoObserver.Monitor( this.service );this.UndoCommand=DelegateCommand.Create() .OnCanExecute( o =>this.service.CanUndo ) .OnExecute( o =>this.service.Undo() ) .AddMonitor( observer );this.RedoCommand=DelegateCommand.Create() .OnCanExecute( o =>this.service.CanRedo ) .OnExecute( o =>this.service.Redo() ) .AddMonitor( observer );this.CreateNewAddressCommand=DelegateCommand.Create() .OnExecute( o => {this.SelectedAddress=this.Entity.Addresses.AddNew(); } );this.DeleteAddressCommand=DelegateCommand.Create() .OnCanExecute( o =>this.SelectedAddress!=null ) .OnExecute( o => {this.SelectedAddress.Delete();this.SelectedAddress=this.Entity.Addresses.FirstOrDefault(); } ) .AddMonitor( PropertyObserver.For( this ).Observe( v =>v.SelectedAddress ) );var person =newPerson() { FirstName ="Mauro", LastName ="Servienti" };person.Addresses.Add( newAddress( person ) { City ="My town", Street ="Where I live" } );var entity =newPersonViewModel();entity.Initialize( person,false );this.service.Attach( entity );this.Entity= entity; }publicICommand UndoCommand { get; privateset; }publicICommand RedoCommand { get; privateset; }publicICommand CreateNewAddressCommand { get; privateset; }publicICommand DeleteAddressCommand { get; privateset; }publicPersonViewModel Entity {get { returnthis.GetPropertyValue( () =>this.Entity ); }privateset { this.SetPropertyValue( () =>this.Entity, value ); } }publicIEntityItemView<AddressViewModel> SelectedAddress {get { returnthis.GetPropertyValue( () =>this.SelectedAddress ); }privateset { this.SetPropertyValue( () =>this.SelectedAddress, value ); } }}