要么是我没有看到解决方案,要么是我在使用 MVVM 时发现了一个陷阱。
我有这个示例主细节:
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 Customers 通过 ...="{Binding Customers}" 绑定到视图,并且当用户更改客户时,相关订单将通过 ItemsSource="{Binding SelectedItem.订单,ElementName=comboboxCustomer}”。
这可以通过 MVVM 实现:
我只需(为简单起见)拨打
Customers.Add(new Customer(){...});
即可添加新客户。
添加后我这样做:
this.RaisePropertyChanged("Customers");
。这将更新视图并立即在客户组合框中显示客户。
现在是 MVVM 不可能的部分了。
我可以通过
SelectedCustomer.Orders.Add(New Order(){...});
添加新订单
但是我不能像以前一样对订单上的客户发起 CollectionChanged/PropertyChanged 事件,因为订单属性未通过公共访问器绑定到视图。
即使我将 Orders 可绑定属性公开给视图,视图本身也关心主从切换而不是 ViewModel...
问题
如何使主从细节与详细信息列表中的添加/删除对象一起工作并在视图上立即更新?
使用主从视图时,这总是很困难。但是,一种选择通常是利用 INotifyPropertyChanged 和 INotifyCollectionChanged,并在 ViewModel 中自行跟踪它们。通过跟踪对象上的这些属性,您可以正确处理通知。
我在博客中谈到了类似的问题,我希望根据详细信息窗格中的值在“主”列表中进行聚合(即:显示订单总数,这将始终是最新的)。问题是相同的。
我在 Expression Code Gallery 上放置了一些 工作代码,演示了如何处理此跟踪,并使所有内容实时更新,同时仍然保持 MVVM 术语中的“纯粹”。
我们最近遇到了类似的问题,但额外要求模型由简单的愚蠢的 POCO 组成。
我们的解决方案是残酷地应用模型-视图模型分离。 Model 和 ViewModel 都不包含
ObservableCollection<ModelEntity>
,相反,Model 包含 POCO 集合,ViewModel 包含 ObservableCollection<DetailViewModel>
。
轻松解决添加、获取、更新。此外,如果只有主从其集合中删除一个详细信息,则会触发适当的事件。 但是,如果详细信息请求删除,则必须向主控(集合的所有者)发出信号。
这可以通过滥用
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 可能永远只需要它们的名称,因此您确实需要保持 ViewModel 的构建成本低廉,并确保列表不会爆炸。
从好的方面来说,您可以确保 UI 仅绑定到 ViewModel 对象,并且可以将大量 INotifyPropertyChanged 内容保留在模型之外,从而在各层之间提供清晰的划分。