要么我没有看到解决方案,要么我在使用MVVM时遇到了陷阱。
我有这个样本Master-Detail:
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}
}
让我们在CustomerOrdersViewModel中假设我的ObservableCollection客户绑定到View via ... =“{Binding Customers}”,当客户从用户更改时,相关订单通过ItemsSource =“{Binding”显示在DataGrid中SelectedItem.Orders,ElementName = comboboxCustomer}“。
这可以通过MVVM实现:
我可以通过简单地(为简单起见)调用Customers.Add(new Customer(){...});
来添加新客户。
添加后,我执行此操作:this.RaisePropertyChanged("Customers");
。这将更新视图并立即在Customer-Combobox中显示客户。
现在MVVM出现了不可能的部分。
我可以按SelectedCustomer.Orders.Add(New Order(){...});
但我不能像以前那样通过订单上的客户提升CollectionChanged / PropertyChanged事件,因为订单属性未通过公共访问者绑定到View。
即使我将Orders bindable属性暴露给视图,视图本身也会关注Master-Detail切换而不是ViewModel ......
问题
如何使Master-Detail在Details-List中使用Add / Del对象并在View上立即更新?
答案 0 :(得分:4)
使用主 - 详细视图时,这一直很困难。但是,一个选项通常是利用INotifyPropertyChanged和INotifyCollectionChanged,并在ViewModel中自己跟踪这些选项。通过在对象上跟踪这些属性,您可以正确处理通知。
我blogged about a similar issue,我希望根据详细信息窗格中的值在“主”列表中进行聚合(即:显示总数为#的订单,总是最新的)。问题是一样的。
我放了一些working code up on the Expression Code Gallery来演示如何处理这种跟踪,让所有内容实时保持最新,同时在MVVM术语中保持“纯粹”。
答案 1 :(得分:0)
我们最近遇到了类似的问题,但附加要求该模型由普通的愚蠢POCO组成。
我们的解决方案是残酷地应用Model-ViewModel分离。模型和ViewModel都不包含ObservableCollection<ModelEntity>
,而模型包含POCO集合,ViewModel包含ObservableCollection<DetailViewModel>
。
轻松解决添加,获取和更新问题。此外,如果只有Master从其集合中删除了一个细节,则会触发正确的事件。 但是,如果要删除的详细信息请求必须发信号通知主人(集合的所有者)。
这可以通过滥用PropertyChanged
事件来完成:
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 }
}
相反,您可以在public event Action<DetailViewModel> Delete;
中引入DetailViewModel
,将其直接绑定到MasterViewModel::Delete
等。
这种方法的缺点是你必须构建许多ViewModel,这些ViewModel可能永远不需要超过它们的Name,所以你真的需要保持ViewModel的构造便宜,并确保列表不会爆炸。
从好处来看,您可以确保UI仅绑定到ViewModel对象,并且可以将大量的INotifyPropertyChanged goop保留在模型之外,从而在各层之间提供干净的切割。