ObservableCollection inside ObservableCollection d

2019-09-14 21:18发布

问题:

For the last three days I've been facing a quite a composite problem. I am trying to make an order taking app, to do this I wanted to have an ObservableCollection containing MainCategory items. Now the MainCategory items contained a CategoryName and another ObservableCollection of dishes. The dishes have those props: name, price and quantity. I did all this and the bindings and everything works perfectly.

The problem is at the bottom of the page (outside the ListView which is nested inside a Listview ) I want to have a TextBlock with the TotalValue that needs to change every time The quantity of a dish changes. I am using MVVM and I have done all the INotifyPropertyChanged to the props and the CollectionChanged, but this only works when I add or remove something. Not when I change the value of a prop in the second ObservableCollection.

Here is some code:

    public class MenuCollection : ObservableCollection<MainCategories>,
INotifyCollectionChanged, INotifyPropertyChanged
{
    public MenuCollection()
    {
        Add(new MainCategories("Category1", false));
        this[0].SubMenuItems.Add(new Dishes("Dish1", 4));

    }
}

 public class MainCategories : MainViewModelBase
{

    public ObservableCollection<Dishes> SubMenuItems{get; set;}  
    public MainCategories(string name, bool visible)
    {
        this.categoryName = name;
        this.visible = visible;
        SubMenuItems = new ObservableCollection<Dishes>();
    }

    public string categoryName;
    public string CategoryName
    {
        get { return categoryName; }
        set
        {
            if (categoryName != value)
            {
                categoryName = value;
                OnPropertyChanged("CategoryName");
            }
        }
    }
    public bool visible;
    public bool Visible
    {
        get { return visible; }
        set
        {
            if (visible != value)
            {
                visible = value;
                OnPropertyChanged("Visible");
            }
        }
    }
}

public class Dishes : MainViewModelBase
{
    public Dishes(string dishName, int price)
    {
        this.dishName = dishName;
        this.dishPrice = price;
        this.quantity = 0;
    }

    private string dishName;
    public string DishName
    {
        get { return dishName; }
        set
        {
            if (dishName != value)
            {
                dishName = value;
                OnPropertyChanged("DishName");
            }
        }
    }
    private int dishPrice;
    public int DishPrice
    {
        get { return dishPrice; }
        set
        {
            if (dishPrice != value)
            {
                dishPrice = value;
                OnPropertyChanged("DishPrice");
            }
        }
    }
    private int quantity;
    public int Quantity
    {
        get { return quantity; }
        set
        {
            if (quantity != value)
            {
                quantity = value;
                OnPropertyChanged("Quantity");
            }
        }
    }

}

I just want to find a way to keep my total value update when anything changes inside the Main observable collection, like the quantity of a product. is there anyway to fire the CollectionChanged event?

this is the UI

and an earlier version without the buttons

回答1:

I would consider possibly not using an observable collection if you need updates when items in the collection change. Observable collection is only useful for noting when items are added or removed from a collection.

Something that I've done before is similar to the code below.

In your MainCategory you would only expose an IEnumerable<Dish> instead of the full list. Then, in MainCategory you can create wrapping methods to add and remove dishes that subscribe to property changed notifications. Then every time a dish changes you can make updates to your aggregate values like Total and notify that your list of dishes has changed.

private List<Dish> _subMenuItems = new List<Dish>();
public IEnumerable<Dish> SubMenuItems { get { return _subMenuItems; } }

public void AddDish(Dish dish)
{
    _subMenuItems.Add(dish);
    dish.PropertyChanged += dish_PropertyChanged;
    OnPropertyChanged("SubMenuItems");
}

public void RemoveDish(Dish dish)
{
    if (_subMenuItems.Contains(dish))
    {
        dish.PropertyChanged -= dish_PropertyChanged;
        _subMenuItems.Remove(dish);
        OnPropertyChanged("SubMenuItems");
    }
}

private double _total;
public double Total
{
    get { return _total; }
    set
    {
        if (_total != value)
        {
            _total = value;
            OnPropertyChanged("Total");
        }
    }
}

private void dish_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    Total = getTotal();
    // any other aggregate calculations can go here.
    OnPropertyChanged("SubMenuItems");
}


回答2:

Another option that might work for you is something I picked up online a few years ago and is quite handy. It is call a "Truly" observable collection and will fire events not only when items are added/removed but also when properties on the items within the list change. I havent tried it with a collection within a collection but it should work

public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler ItemPropertyChanged;

    public TrulyObservableCollection()
        : base()
    {
        CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
    }

    public TrulyObservableCollection(IEnumerable<T> pItems)
        : this()
    {
        foreach (var item in pItems)
        {
            this.Add(item);
        }
    }

    void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
    }

    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(a);

        if (ItemPropertyChanged != null)
        {
            ItemPropertyChanged(sender, e);
        }
    }
}