2010-02-27 72 views
6

要么我没有看到解决方案,要么发现使用MVVM时存在一个缺陷。使用Master-Detail场景的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客户通过... =绑定到视图“{结合客户}”,当顾客从改变用户的相关订单通过ItemsSource =“{Binding SelectedItem.Orders,ElementName = comboboxCustomer}”显示在DataGrid中。

这是可能的MVVM:

我可以通过简单地增加一个新的客户(为简单起见)调用Customers.Add(new Customer(){...});

添加后,我这样做:this.RaisePropertyChanged("Customers");。这将更新视图并立即在Customer-Combobox中显示客户。

现在来MVVM的不可能的部分。

我可以SelectedCustomer.Orders.Add(New Order(){...});

添加一个新的订单,但我不能现在订单养CollectionChanged/PropertyChanged事件像之前与客户因为订单地产不是通过公共的访问必将查看。

即使我会暴露订单绑定属性到视图,视图本身关心的主从切换不是在ViewModel ...

问题

怎么可能使主 - 细节列表中的添加/删除对象的详细工作,并立即更新视图?

回答

4

使用主从视图时,这总是很困难。但是,一种选择通常是利用INotifyPropertyChanged和INotifyCollectionChanged,并在ViewModel中自己跟踪这些选项。通过跟踪对象上的这些属性,可以正确处理通知。

blogged about a similar issue,其中我想根据详细信息窗格中的值(即:显示订单总数,总是最新)在“主”列表中发生聚合。问题是相同的。

我放了一些working code up on the Expression Code Gallery表明你如何处理这个跟踪,并使所有的东西保持最新的实时,而仍然保持MVVM条款“纯”。

+1

我检查了你的代码,必须说它太复杂了。 WPF很好。 MVVM在WinForms中使事情变得过于复杂。如果你问我,MVVM就不可能使用实体框架。尝试使用MVVM进行Eager加载,然后您知道我的意思。 每个LOB都有大量的主细节。现在我知道为什么每个MVVM样本都有一个愚蠢而纯粹的显示所有客户列表演示... 如果您知道另一个MVVM Master-Detail示例,我将不胜感激链接:) 对于那些对此事有兴趣的人: http://www.codeproject.com/KB/WPF/WpfNhibernateToolkit.aspx – msfanboy 2010-02-27 20:38:04

+1

MVVM与View和ViewModel之间的关系处理很多,但关于VM的关系模型完全默默地猜测为什么?您从DAL获取数据并阅读3 ObservableCollection 中的相关客户,订单,产品?这是一个很大的努力,创造一种entityViewModel上下文......似乎太多的开发者使用VS2010 wpf RAD设计工具来实现真正的LOB应用程序需要更多... – msfanboy 2010-02-27 20:50:14

+0

啊 - 但我不同意这里。是的,代码很复杂,但它是完全可重用的。使用它只是将一个行为拖到你的主列表上,并且它“正常工作”。 – 2010-02-27 20:56:03

0

我们最近遇到了一个类似的问题,但还有一个额外的要求,即模型由简单的POCO组成。

我们的解决方案是残酷地应用Model-ViewModel分离。 Model和ViewModel都不包含ObservableCollection<ModelEntity>,而是Model包含POCO集合,ViewModel包含ObservableCollection<DetailViewModel>

这很容易解决添加,获取和更新。另外,如果只有主人从其集合中删除了一个细节,那么正确的事件将被触发。 但是,如果详细请求被删除,它必然需要发信号给主人(集合的所有者)。

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 } 
} 

相反,你可以在DetailViewModel引入public event Action<DetailViewModel> Delete;,绑定直接到MasterViewModel::Delete

的缺点这种方法是:

这可以通过滥用一个PropertyChanged事件中完成你必须构造很多ViewModel,这些ViewModel可能永远不会超过它们的Name,所以你确实需要保持ViewModel的构造便宜,并确保列表不会被爆炸。

在好处上你可以确保UI只绑定到ViewModel对象,并且你可以保留很多INotifyPropertyChanged goop出你的模型,给你一个干净的剪切层。