【问题标题】:A MVVM pitfall using Master-Detail scenario使用 Master-Detail 场景的 MVVM 陷阱
【发布时间】:2010-02-27 19:53:29
【问题描述】:

要么我没有看到解决方案,要么我发现了使用 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 客户通过 ...="{Binding Customers}" 绑定到视图,并且当客户从用户更改时,相关订单通过 ItemsSource="{Binding 显示在 DataGrid 中SelectedItem.Orders, ElementName=comboboxCustomer}"。

这可以通过 MVVM 实现:

我可以通过简单地(为简单起见)调用Customers.Add(new Customer(){...});来添加新客户。

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

现在是 MVVM 不可能完成的部分。

我可以通过SelectedCustomer.Orders.Add(New Order(){...});添加新订单

但我不能像以前一样在订单上引发 CollectionChanged/PropertyChanged 事件,因为订单属性未通过公共访问器绑定到视图。

即使我将 Orders 可绑定属性公开给视图,视图本身也关心 Master-Detail 切换而不是 ViewModel...

问题

如何使 Master-Detail 与 Details-List 中的 Add/Del 对象一起工作并在 View 上立即更新?

【问题讨论】:

    标签: .net mvvm master-detail


    【解决方案1】:

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

    blogged about a similar issue,我希望根据详细信息窗格中的值在“主”列表中进行聚合(即:显示订单总数,这将始终是最新的)。问题是相同的。

    我放了一些 working code up on the Expression Code Gallery 来演示如何处理这种跟踪,并使所有内容实时保持最新状态,同时在 MVVM 术语中仍然保持“纯粹”。

    【讨论】:

    • 我已经检查了你的代码,并且必须说它让一切变得太复杂了。 WPF 很好。 MVVM 使 WinForms 中的事情变得过于复杂。如果你问我,MVVM 无法实现实体框架是个笑话。尝试使用 MVVM 进行 Eager Loading,然后你就会明白我的意思了。每个 LOB 都有大量的 Master-Detail。现在我知道为什么每个 MVVM 样本都有一个愚蠢而简单的 Show all customers list demo...如果你知道另一个 MVVM Master-Detail 样本,我会很感激一个链接:)
    • MVVM 处理了很多 View 和 ViewModel 之间的关系,但关于 VM 与 Model 的关系,它完全默默地猜测为什么?您从 DAL 获取数据并在 3 ObservableCollection 中读取相关客户、订单、产品?这是创建某种 entityViewModel 上下文的巨大努力...似乎有太多开发人员使用 VS2010 wpf RAD 设计器工具来实现真正的 LOB 应用程序需要更多...
    • 啊-但我不同意这里。是的,代码很复杂,但它是完全可重用的。使用它只是将一个行为拖到你的主列表中,它“正常工作”。
    • 顺便说一句。我对你的 probject 有问题。我添加了交互性 .dll + 必须使用上述带有 v4.0 和 VS2010 的 dll 重建到 .net 4.0。仍然无法编译...无法识别 xaml 中的交互标记。老问题 xaml 又出现了 ;-)
    • 好的忘记全部转换为.net 4.0 并添加更多.dll 的作品,现在将有更深入的了解:)
    【解决方案2】:

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

    我们的解决方案是粗暴地应用 Model-ViewModel 分离。 Model 和 ViewModel 都不包含 ObservableCollection&lt;ModelEntity&gt;,而是 Model 包含 POCO 集合,ViewModel 包含 ObservableCollection&lt;DetailViewModel&gt;

    这很容易解决添加、获取和更新。此外,如果只有 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 }
    }
    

    相反,您可以在DetailViewModel 中引入public event Action&lt;DetailViewModel&gt; Delete;,将其直接绑定到MasterViewModel::Delete,等等。

    这种方法的缺点是您必须构建许多 ViewModel,这些 ViewModel 可能永远不需要超过它们的名称,因此您确实需要保持 ViewModel 的构建成本低廉,并确保列表不会爆炸。

    从好的方面来说,您可以确保 UI 仅绑定到 ViewModel 对象,并且您可以将大量 INotifyPropertyChanged goop 保留在您的模型之外,让您在层之间有一个清晰的界限。

    【讨论】:

      猜你喜欢
      • 2010-11-14
      • 1970-01-01
      • 2017-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-07
      • 1970-01-01
      相关资源
      最近更新 更多