Лучший способ привязки данных (стиль WPF / Silverlight) к агрегации свойства в коллекции внутри коллекции? - PullRequest
0 голосов
/ 01 июля 2010

У меня есть такая объектная модель:

class Car
{
    public string Manufacturer;
    public int Mileage;
    public ObservableCollection<Part> Parts;
}

class Part
{
    public string Name;
    public int Price;
}

И я хочу показать пользователю полную стоимость «всех моих автомобилей». Я хочу сделать это с помощью DataBinding (WPF / Silverlight / XAML). Вот код, который я хочу написать:

class MyWindow : Window
{
    public MyWindow()
    {
        ObservableCollection<Car> myCars = CreateCars();  // Create a collection of cars

        // Let the user edit the collection of Cars
        this.DataGrid.DataContext = myCars;

        // Bind to the grand total Price of all Parts in each Car.
        // Should update text automatically if...
        // 1) The Price of an individual Part changes
        // 2) A Part is added or removed from a Car
        // 3) A Car is added or removed from myCars collection
        this.TotalPriceOfMyCarsLabel.Text = ???  // How?
    }
}

Подход MVVM, кажется, является своего рода шаблоном, который был бы здесь полезен, но я не могу придумать лучший способ его реализации. Моя объектная модель может быть изменена, например, чтобы добавить INotifyPropertyChanged и тому подобное.

Код, который я пытался написать, соответствует духу ObservableCollection, который также отслеживает изменения элементов в коллекции , но код становится достаточно длинным, и все обработчики событий OnCollectionChanged и OnPropertyChanged летают повсюду.

Мне бы очень хотелось использовать привязку данных для обработки расчета и отображения общей цены, как я могу сделать это аккуратно?

Спасибо!

-Mike

1 Ответ

3 голосов
/ 01 июля 2010

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

Это может быстро усложниться, когда вы пишете свойства и коллекции, которые «делают недействительными» другие вычисляемые свойства. В моих приложениях я использовал подход, основанный на атрибутах, который позволяет мне украшать свойство атрибутом DependsOn, чтобы при каждом изменении зависимого свойства также вызывалось событие изменения свойства для вычисляемого свойства. В твоем случае я не думаю, что тебе нужно изо всех сил.

Но одна вещь, которая, я думаю, действительно пригодится, это класс, производный от ObservableCollection, который из-за отсутствия лучшего термина в данный момент я назову ObservableCollectionEx. Единственное, что он добавляет, - это событие ItemPropertyChanged, которое позволяет подключить к нему один обработчик, и оно будет вызываться всякий раз, когда изменяется свойство элемента внутри коллекции. Это требует от вас подключения до INotifyPropertyChanged каждый раз, когда элемент добавляется или удаляется. Но как только этот класс уходит, каскадные изменения свойств становятся легким делом.

Я бы посоветовал вам создать CarListViewModel (или как вы хотите его называть), который напоминает следующее. Обратите внимание, что конечным результатом является иерархия объектов, в которой любые изменения свойства чтения / записи Part.Total приводят к цепной реакции событий PropertyChanged, которые всплывают до ViewModel. Код ниже не является полным. Вам необходимо предоставить реализацию INotifyPropertyChanged. Но в конце вы увидите ObservableCollectionEx, о котором я упоминал.

РЕДАКТИРОВАТЬ: Я только что нажал на ссылку в вашем исходном сообщении и понял, что у вас уже есть реализация ObservableCollectionEx. Я решил использовать методы InsertItem, RemoveItem и т. Д. Вместо OnCollectionChanged, чтобы перехватить / отцепить событие элемента, потому что в другой реализации есть неприятная проблема - если вы очистите коллекцию, у вас больше не будет коллекции элементов для отсоединения.

class CarListViewModel : INotifyPropertyChanged {

    public CarListViewModel() {

        Cars = new ObservableCollectionEx<Car>();
        Cars.CollectionChanged += (sender,e) => OnPropertyChanged("Total");
        Cars.ItemPropertyChanged += (sender,e) => {
            if (e.PropertyName == "Total") {
                OnPropertyChanged("Total");
            }
        }

    }

    public ObservableCollectionEx<Car> Cars {
        get;
        private set;
    }

    public decimal Total {
        get {
            return Cars.Sum(x=>x.Total);
        }
    }

}

class Car : INotifyPropertyChanged {

    public Car() {
        Parts = new ObservableCollectionEx<Part>();
        Parts.CollectionChanged += (sender,e) => OnPropertyChanged("Total");
        Parts.ItemPropertyChanged += (sender,e) => {
            if (e.PropertyName == "Total") {
                OnPropertyChanged("Total");
            }
        }
    }

    public ObservableCollectionEx<Part> Parts {
        get;
        private set;
    }

    public decimal Total {
        get {
            return Parts.Sum(x=>x.Total);
        }
    }

}

class Part : INotifyPropertyChanged {

    private decimal _Total;

    public decimal Total {
        get { return _Total; }
        set {
            _Total = value;
            OnPropertyChanged("Total");
        }
    }

}

class ObservableCollectionEx<T> : ObservableCollection<T> 
    where T: INotifyPropertyChanged
{

    protected override void InsertItem(int index, T item) {
        base.InsertItem(index, item);
        item.PropertyChanged += Item_PropertyChanged;
    }

    protected override void RemoveItem(int index) {
        Items[index].PropertyChanged -= Item_PropertyChanged;
        base.RemoveItem(index);
    }

    protected override void ClearItems() {
        foreach (T item in Items) {
            item.PropertyChanged -= Item_PropertyChanged;
        }
        base.ClearItems();
    }

    protected override void SetItem(int index, T item) {

        T oldItem = Items[index];
        T newItem = item;

        oldItem.PropertyChanged -= Item_PropertyChanged;
        newItem.PropertyChanged += Item_PropertyChanged;

        base.SetItem( index, item );

    }

    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) {
        var handler = ItemPropertyChanged;
        if (handler != null) { handler(sender, e); }
    }

    public event PropertyChangedEventHandler ItemPropertyChanged;

}
...