Свойство MVVM зависит от графа объектов - PullRequest
5 голосов
/ 06 декабря 2011

Я работаю с WPF+MVVM.

У меня есть VM, который содержит свойство Customer. Customer имеет ObservableCollection из Orders. Каждый Order имеет ObservableCollection Items. Каждый Items имеет Price.

Теперь у меня есть следующее свойство на VM:

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

Проблема заключается в том, что всякий раз, когда происходит изменение в любой точке этого графа объектов - пользовательский интерфейс должен быть уведомлен о том, что TotalPrice изменился - но это не так ...

Например, если Customer будет изменено с A на B, или будет добавлен заказ, или элемент будет удален, или цена элемента будет изменена и т. Д.

У кого-нибудь есть изящное решение для этого?

Спасибо.

Ответы [ 4 ]

3 голосов
/ 06 декабря 2011

Поддерживали ли вы INotifyPropertyChanged / INotifyCollectionChanged интерфейсы во ViewModels? Вы должны иметь возможность вызывать любое свойство вручную, например, в установщике любого свойства, которое вы можете запускать OnPropertyChanged("TotalPrice"), так что привязки пользовательского интерфейса для TotalPrice также будут обновлены.

Для обработки изменений зависимых объектов вы можете предоставить события или что-то в этом роде, чтобы ViewModel мог подписываться и обрабатывать изменения базовых объектов, например, у вас есть какая-то служба, которая выполняет перезагрузку Orders из базы данных, например Так что, как новые изменения приходят, вы также обновите пользовательский интерфейс. В этом случае OrdersService должен выставить event OrdersUpdated, и ViewModel может подписаться на это событие и в триггере PropertyChanged события для затронутых свойств.

Давайте рассмотрим в качестве примера какой-то случай, например, была изменена цена ордера. Кто отвечает за эти изменения? Это сделано пользователем через пользовательский интерфейс?

1 голос
/ 06 декабря 2011

Вы можете найти здесь интересное сообщение, написанное мной несколько дней назад, в котором говорится именно об этой проблеме (и ее решении ...)

0 голосов
/ 07 декабря 2011

Я думаю, что мои последние попытки WPF MadProps довольно хорошо справляются с этим сценарием.Взгляните на этот пример сценария мастер-детализации .Пока есть путь от редактируемого элемента до виртуальной машины (например, 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 и элементов в них может быть сложным.

0 голосов
/ 06 декабря 2011

Вы можете реализовать свойства «аккумулятора», которые хранят сумму значений в коллекции объектов.Прислушайтесь к изменениям этих значений и обновите аккумулятор соответствующим образом.
(Кстати, я забыл упомянуть, что это действительно просто для ситуаций, когда значение является дорогостоящим для вычисления. В противном случае ответ 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 */ }
    }
}
...