数据绑定(WPF / Silverlight样式)到集合内集合的属性聚合的最佳方法是什么?

时间:2010-07-01 02:26:15

标签: c# wpf silverlight data-binding

我有一个像这样的对象模型:

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 that also monitors changes on the elements in collection的精神,但代码变得很长,所有的OnCollectionChanged和OnPropertyChanged事件处理程序随处可见。

我真的很想使用DataBinding来处理总价的计算和显示,我该怎么做才能干净利落?

谢谢!

-Mike

1 个答案:

答案 0 :(得分:3)

我强烈推荐使用MVVM,但是当谈到像这样的层次模型时,如果你愿意并且能够对模型类本身进行一些简单的修改,你可以省去很多悲伤。

当您编写“使其他计算属性无效”的属性和集合时,它会很快变得复杂。在我的应用程序中,我使用了一种基于属性的方法,它允许我使用DependsOn属性修饰属性,这样每当更改依赖属性时,也会为计算属性引发属性更改事件。在你的情况下,我认为你不需要全力以赴。

但我认为真正派上用场的一件事是一个源自ObservableCollection的课程,由于目前缺少一个更好的术语,我会称之为ObservableCollectionEx。它添加的一件事是ItemPropertyChanged事件,它允许您将一个处理程序连接到它,并且只要集合中项目的属性发生更改,它就会被引发。这需要您在添加或删除项目时连接到INotifyPropertyChanged。但是,一旦这个课程失控,级联的属性变化就变得轻而易举。

我建议您创建类似于以下内容的CarListViewModel(或任何您想要调用的内容)。请注意,最终结果是对象的层次结构,其中对读/写Part.Total属性的任何修改都会导致PropertyChanged事件的链式反应,这些事件会冒泡到ViewModel。以下代码并不完整。您需要提供INotifyPropertyChanged的实现。但最后你会看到我提到的ObservableCollectionEx。

编辑:我刚刚点击了原帖中的链接,发现你已经有了ObservableCollectionEx的实现。我选择使用InsertItem,RemoveItem等方法而不是OnCollectionChanged来挂钩/取消挂钩项事件,因为在其他实现中存在一个令人讨厌的问题 - 如果清除集合,则不再有取消挂钩的项目集合。 p>

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;

}