MVVM Trap Using Master-Detail Script

Either I do not see the solution, or I found an error when using MVVM.

I have this Master-Detail sample:

class Customer { int CustomerID {get;set} string Name {get;set} ObservableCollection<Order> Orders {get;set} } class Order { int OrderID {get;set} int Quantity {get;set} double Discount {get;set} } 

Suppose that in my CustomerOrdersViewModel my ObservableCollection clients are bound to the view through ... = "{Binding Customers}", and when the client is changed from the user, the related Orders are displayed in the DataGrid via ItemsSource = "{Binding SelectedItem.Orders, ElementName = comboboxCustomer} "

This is possible using MVVM:

I can add a new customer simply (for simplicity) by calling Customers.Add(new Customer(){...}); .

After adding, I do this: this.RaisePropertyChanged("Customers"); . This will update the presentation and immediately show the Customer in Customer-Combobox.

Now the impossible part appears with MVVM.

I can add a new order SelectedCustomer.Orders.Add(New Order(){...});

BUT, I cannot raise the CollectionChanged / PropertyChanged event, as before, with the Clients now in the Order, because the Orders property is not tied to the view through a public accessor.

Even if I set the property of binding Orders to the view, the view itself takes care of switching the Master-Detail, and not in the ViewModel ...

Question

How can Master-Detail work with Add / Del objects in the Details-List and update immediately in the view?

+6
mvvm master-detail
source share
2 answers

It is always difficult when you work with master-detail views. However, one option, as a rule, is to use INotifyPropertyChanged and INotifyCollectionChanged and track them yourself in the ViewModel. By tracking these properties on your objects, you can correctly process notifications.

I wrote about a similar problem , where I wanted the aggregation to occur in the "leading" list based on the values ​​in the details area (that is, to show the total number of orders, which will always be relevant). The problems are identical.

I put some working code into the Expression Code gallery , demonstrating how you can handle this tracking, and do everything possible to keep abreast of events in real time, while remaining β€œclean” in MVVM conditions.

+4
source share

We recently ran into a similar problem, but with the additional requirement that the model consist of simple stupid POCOs.

Our solution is to brutally apply Model-ViewModel separation. Neither the Model nor the ViewModel contain an ObservableCollection<ModelEntity> , instead the model contains a POCO collection, and the ViewModel contains an ObservableCollection<DetailViewModel> .

This easily resolves Add, Get, and Update. Also, if only the Master removes the part from the collection, the corresponding events are triggered. However, if the request details must be removed, it is imperative to inform the master (collection owner).

This can be done by breaking the PropertyChanged event:

 class MasterViewModel { private MasterModel master; private ISomeService service; private ObservableCollection<DetailViewModel> details; public ObservableCollection<DetailViewModel> Details { get { return this.details; } set { return this.details ?? (this.details = LoadDetails()); } } public ObservableCollection<DetailViewModel> LoadDetails() { var details = this.service.GetDetails(master); var detailVms = details.Select(d => { var vm = new DetailViewModel(service, d) { State = DetailState.Unmodified }; vm.PropertyChanged += this.OnDetailPropertyChanged; return vm; }); return new ObservableCollection<DetailViewModel>(detailVms); } public void DeleteDetail(DetailViewModel detailVm) { if(detailVm == null || detailVm.State != DetailState.Deleted || this.details == null) { return; } detailVm.PropertyChanged -= this.OnDetailPropertyChanged; this.details.Remove(detailVm); } private void OnDetailPropertyChanged(object s, PropertyChangedEventArgs a) { if(a.PropertyName == "State" & (s as DetailViewModel).State == DetailState.Deleted) { this.DeleteDetail(s as DetailViewModel); } } } class DetaiViewModel : INotifyPropertyChanged { public DetailState State { get; private set; } // Notify in setter.. public void Delete() { this.State = DetailState.Deleted; } public enum DetailState { New, Unmodified, Modified, Deleted } } 

Instead, you can enter public event Action<DetailViewModel> Delete; in DetailViewModel , bind it directly to MasterViewModel::Delete , etc.

The disadvantage of this approach is that you need to build a lot of ViewModels that you might never need for more than their name, so you really need to keep the ViewModels construct cheaper and make sure the list doesn't explode.

At the top, you can make sure that the user interface is only associated with ViewModel objects, and you can save a lot of INotifyPropertyChanged goop from your model, giving you a clean slice between layers.

0
source share

All Articles