【问题标题】:MVVM property depends on a graph of objectsMVVM 属性取决于对象图
【发布时间】:2012-01-14 05:34:24
【问题描述】:

我正在与WPF+MVVM 合作。

我有一个VM,其中包含一个Customer 属性。 Customer 有一个 ObservableCollectionOrders。每个Order 都有一个ObservableCollectionItems。每个Items 都有一个Price

现在,我的VM 上有以下属性:

public double TotalPrice
{
    return Customer.Orders.Sum(x => x.Items.Sum(y => y.Price));
}

问题在于,无论何时在此对象图中的任何点发生更改 - 都应通知 UI TotalPrice 已更改 - 但它没有...

例如,如果Customer将从A更改为B,或添加订单,或删除商品,或更改商品价格等。

有人对此有一个优雅的解决方案吗?

谢谢。

【问题讨论】:

  • 这些对象的变化来自哪里?是否存在某种分层 GUI 布局并且用户正在修改订单?还是这些更改来自持久性/域/其他非 GUI 来源?
  • 嗨 Erik,这些变化来自 UI 本身,这意味着用户可以更改商品的价格、添加/删除商品、添加/删除订单等。
  • 在这种情况下,我会说 sll 有一个很好的解决方案。 VM 可以侦听 Orders 和 Items 上的集合更改事件,并且该处理程序可以引发 TotalPrice 上的属性更改。至于价格被改变,你还需要监听 Item.Price 上的属性改变,它的处理程序也会提高属性改变的总数。
  • 嗨,Eirk,您还应该看看 ItzikSaban 给我的解决方案,因为它似乎包含了您刚才提到的所有逻辑。
  • 根据我的口味,接线有点重,但这是一个有趣的概念。我个人使用 NotifyChange(Expression> propertyExpression) 范例,它允许您调用 NotifyChange(() => PropertyName)。如果你然后参数化它,你可以做 NotifyChange(() => Prop1, () => Prop2, etc)。 Itzik 解决方案的流畅界面很酷,但我不知道我是否想在 VM 的构造函数中为用户可能永远不会调用的属性执行所有这些连线工作。不过,也许我会自己尝试一下,然后变得更喜欢它。

标签: c# wpf data-binding mvvm observablecollection


【解决方案1】:

我认为我最新的 WPF 工作 MadProps 很好地处理了这种情况。看看this example master-detail scenario。只要存在从正在编辑的 Item 到 VM 的路径(例如,VM.TheCustomer.SelectedOrder.SelectedItem 或简单的 VM.SelectedItem),PropChanged 事件就会传播到 VM,您可以编写:

public readonly IProp<Customer> TheCustomer;
public readonly IProp<double> TotalPrice;

protected override void OnPropChanged(PropChangedEventArgs args)
{
    if (args.Prop.IsAny(TheCustomer, Item.Schema.Price))
    {
        TotalPrice.Value = TheCustomer.Value.Orders
            .Sum(order => order.Items.Sum(item => item.Price.Value));
    }
}

关于添加和删除项目或订单,我只想在ICommand 代码中进行显式更新,例如VM.UpdateTotalPrice();。管理对 ObservableCollections 及其中的项目的订阅和取消订阅可能会很棘手。

【讨论】:

    【解决方案2】:

    您是否在 ViewModels 中支持 INotifyPropertyChanged / INotifyCollectionChanged 接口?您应该能够手动触发任何属性,例如在任何属性的设置器中您可以触发OnPropertyChanged("TotalPrice"),因此TotalPrice 的 UI 绑定也会更新。

    要处理依赖对象的更改,您可以提供事件或类似的东西,以便 ViewModel 能够订阅和处理底层对象更改,例如,您有一些服务正在改变从数据库重新加载订单,例如当新的变化到来时,你也会更新 UI。在这种情况下,OrdersService 应该公开 event OrdersUpdated 并且 ViewModel 可以订阅此事件并触发 PropertyChanged 受影响属性的事件。

    让我们以某些情况为例,例如订单价格已更改。谁负责这种变化?这是由用户通过 UI 完成的吗?

    【讨论】:

    • 或者,一种更简洁的方法是使用某种 PropertyObserver,如下所示:joshsmithonwpf.wordpress.com/2009/07/11/…
    • 我在单元测试中使用了某种观察者,例如您可以订阅PropertyChanged 事件,然后在事件处理程序中检查属性名称以确保特定属性已更改。
    【解决方案3】:

    您可以实现“累加器”属性,将值的总和存储在对象集合中。监听这些值的变化并适当地更新累加器。
    (顺便说一句 - 我忘了提到这个真的只是用于计算价值昂贵的情况。否则 Sll 的答案绝对是要走的路。)

    像这样,把无聊的东西排除在外:

        class BasketOfGoods : INotifyPropertyChanged
    {
        ObservableCollection<Good> contents = new ObservableCollection<Good>();
    
        public decimal Total
        {
            get { /* getter code */ }
            set { /*setter code */ }
        }
    
        public BasketOfGoods()
        {
            contents.CollectionChanged += contents_CollectionChanged;
        }
    
        void contents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            foreach (var newGood in e.NewItems) ((Good)newGood).PropertyChanged += BasketOfGoods_PropertyChanged;
        }
    
        void BasketOfGoods_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Price") Total = contents.Select(x => x.Price).Sum();
        }
    
    }
    
    class Good : INotifyPropertyChanged
    {
        public decimal Price
        {
        {
            get { /* getter code */ }
            set { /*setter code */ }
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以找到here 几天前我写的一篇有趣的帖子,它准确地谈到了这个问题(及其解决方案......)

      【讨论】:

      • 嗨 ItzikSaban,我已经尝试过您的解决方案,它看起来就像我正在寻找的东西。谢谢:)
      猜你喜欢
      • 2014-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-12
      • 2018-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多