6

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

4

2 回答 2

4

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

在博客上写了一个类似的问题,我希望根据详细信息窗格中的值在“主”列表中进行聚合(即:显示订单总数,这将始终是最新的)。问题是相同的。

在 Expression Code Gallery 上放了一些工作代码,展示了如何处理这种跟踪,并使所有内容实时保持最新,同时在 MVVM 术语中仍然保持“纯”。

于 2010-02-27T20:01:20.417 回答
0

我们最近遇到了类似的问题,但附加要求模型由简单愚蠢的 POCO 组成。

我们的解决方案是粗暴地应用 Model-ViewModel 分离。Model 和 ViewModel 都不包含ObservableCollection<ModelEntity>,而是 Model 包含 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 可能永远不需要超过它们的名称,因此您确实需要保持 ViewModel 的构建成本低廉,并确保列表不会爆炸。

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

于 2013-03-07T15:30:15.087 回答