2010-02-27 28 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出你的模型,給你一個乾淨的剪切層。