MVVM 属性取决于对象图

发布于 2024-12-19 14:24:16 字数 641 浏览 2 评论 0 原文

我正在使用 WPF+MVVM

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

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

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

问题是每当此对象图中的任何点发生更改时 - 应通知 UI TotalPrice 已更改- 但事实并非如此...

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

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

谢谢。

I am working with WPF+MVVM.

I have a VM which contains a Customer property. The Customer has an ObservableCollection of Orders. Each Order has an ObservableCollection of Items. Each Items has a Price.

Now, I have the following property on my VM:

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

The problem is whenever a change occurs at any point in this graph of objects - the UI should be notified that TotalPrice had changed - but it doesn't...

For example if the Customer will be altered from A to B, or an order will be added, or an item will be deleted, or an item's price will be altered etc.

Does anyone has an elegant solution for this?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

世俗缘 2024-12-26 14:24:16

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

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

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

Have you supported INotifyPropertyChanged / INotifyCollectionChanged interfaces in ViewModels? You should be able trigger any property manually, for instance in setter of any property you can trigger OnPropertyChanged("TotalPrice") so UI bindings for TotalPrice would be updated as well.

To handle dependent objects changes you can provide events or something like that so ViewModel would be able to subscribe and handle underlying object changes, for instance you have some service which is in chanrge of reloading of the Orders from a database, so as soo as new changes come you would update UI as well. In this case OrdersService should expose event OrdersUpdated and ViewModel can subscribe for this event and in trigger PropertyChanged events for affected properties.

Let's consider some case as an example, for instance Order price has been changed. Who is in charge of this changes? Is this done via UI by an user?

木落 2024-12-26 14:24:16

你可以在这里找到我几天前写的一篇有趣的文章,内容完全正确。关于这个问题(及其解决方案......)

You can find here an interesting post written by me few days ago and it talks exactly about this problem (and its solution...)

浮云落日 2024-12-26 14:24:16

您可以实现“累加器”属性,它将值的总和存储在对象集合中。监听这些值的变化并适当更新累加器。
(顺便说一句 - 我忘了提及,这实际上只适用于计算价值昂贵的情况。否则 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 */ }
    }
}

You might implement "accumulator" properties which store the sum of values in a collection of objects. Listen for changes to those values and update the accumulator appropriately.
(BTW - I forgot to mention that this one is really just for situations where the value is expensive to calculate. Otherwise Sll's answer is definitely the way to go.)

Something like this, with the boring stuff left out:

    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 */ }
    }
}
顾忌 2024-12-26 14:24:16

我认为我最新的 WPF 尝试 MadProps 可以很好地处理这种情况。看一下这个主从场景示例。只要存在从正在编辑的 Item 到 VM 的路径(例如,VM.TheCustomer.SelectedOrder.SelectedItem 或简单的 VM.SelectedItem),PropChanged 事件就会传播到虚拟机,您可以编写:

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 及其中的项目的订阅和取消订阅可能很棘手。

I think my latest WPF endeavor MadProps handles this scenario pretty well. Take a look at this example master-detail scenario. As long as there a path from the Item being edited to the VM (for example, VM.TheCustomer.SelectedOrder.SelectedItem or simply VM.SelectedItem), the PropChanged event will propogate up to the VM and you can write:

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));
    }
}

Regarding adding and removing Items or Orders, I would just put an explicit update in the ICommand code, something like VM.UpdateTotalPrice();. Managing subscriptions and unsubscriptions to ObservableCollections and the items in them can be tricky.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文