使用 Master-Detail 场景的 MVVM 陷阱

问题描述 投票:0回答:2

要么是我没有看到解决方案,要么是我在使用 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...

问题

如何使主从细节与详细信息列表中的添加/删除对象一起工作并在视图上立即更新?

.net mvvm master-detail
2个回答
4
投票

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

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

我在 Expression Code Gallery 上放置了一些 工作代码,演示了如何处理此跟踪,并使所有内容实时更新,同时仍然保持 MVVM 术语中的“纯粹”。


0
投票

我们最近遇到了类似的问题,但额外要求模型由简单的愚蠢的 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 内容保留在模型之外,从而在各层之间提供清晰的划分。

© www.soinside.com 2019 - 2024. All rights reserved.