1

I'm new to WPF and MVVM (and programming in general), so sorry in advance for noobish question, but i really want to make this work in MVVM

I have a ObservableCollection class and ViewModel where i use it, now i want to calculate OreMineTotal and OreTransportTotal in ViewModel every time when i change "Role" in my UI (two way binding).

This works now but only when I add or remove item from ObservableCollection.

1st queston Why is setter for Players ObservableCollection in ViewModel not firing when i change Player role in UI.

2nd How to solve this in MVVM, i would like to know how to intercept/subscribe to RaisePropertyChanged("Role") that is in Player class from ViewModel

In winforms i would handle it in (mouse click or whatever) event where i change player role, but i want to do it MVVM style

namespace MiningCalc.Model
{
public class Player : INotifyPropertyChanged
{

    public Player()
    {
        items = new ObservableCollection<MinedItems>();
    }
    private string name;
    private string mass;
    private int fc;
    private int boost;
    private int log;
    private int role;
    private ObservableCollection<MinedItems> items;

    public ObservableCollection<MinedItems> Items
    {
        get
        {
            return items;
        }
        set
        {
            if (items != value)
            {
                items = value;
                RaisePropertyChanged("Items");
            }
        }
    }

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (name != value)
            {
                name = value;
                RaisePropertyChanged("Name");
            }
        }
    }

    ...... other variables .....

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
    }
}

}

And ViewModel

namespace MiningCalc.ViewModel
{
class ViewModelPlayer : INotifyPropertyChanged
{
    private decimal oreMineTotal;
    private decimal oreTransportTotal;
    private ObservableCollection<Player> players;

    public ObservableCollection<Player> Players
    {
        get
        {
            return players;
        }
        set
        {
            players = value;
            OnPropertyChanged("Players");
            OnPropertyChanged("OreMineTotal"); //not working
            OnPropertyChanged("OreTransportTotal"); // not working
        }
    }

    public ViewModelPlayer()
    {
        players = new ObservableCollection<Player>();
        players.CollectionChanged += OnCollectionChanged;
        LoadLogCommand = new RelayCommand(LoadLog, LoadLog_CanExecute);
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //System.Windows.MessageBox.Show("Firing");
        OnPropertyChanged("OreMineTotal");
        OnPropertyChanged("OreTransportTotal");
    }

    public decimal OreMineTotal
    {
        get
        {
            oreMineTotal = Players.Where(x => x.Role == 0).Sum(a => decimal.Parse(a.Mass, NumberStyles.Any));
            return oreMineTotal;
        }
        set
        {
            oreMineTotal = value;
            OnPropertyChanged("OreMineTotal");
        }
    }


    public decimal OreTransportTotal
    {
        get
        {
            oreTransportTotal = Players.Where(x => x.Role == 1).Sum(a => decimal.Parse(a.Mass, NumberStyles.Any));
            return oreTransportTotal;
        }
        set
        {
            oreTransportTotal = value;
            OnPropertyChanged("OreTransportTotal");
        }
    }
    ...........
ostomac
  • 35
  • 1
  • 6
  • public Player() { Items = new ObservableCollection(); } also, "Items" is colored light cyan in the definition, meaning it is the name of a class or a property, you might want to look at that. Maybe not, maybe its a stackoverflow format issue. – Ricardo Olivo Poletti Jun 30 '17 at 18:33
  • Yes, this is another ObservableCollection class inside Player one – ostomac Jun 30 '17 at 18:41
  • huh? I dont think you understand my point. In your constructor, you should set the public member of your property (Items) not the private one (items), because there is no private notification. Also, the property name "Items" seems to be a different class, try changing it to "Items2", just to be sure. – Ricardo Olivo Poletti Jun 30 '17 at 19:09
  • Mea culpa, fixed it now. Thx – ostomac Jun 30 '17 at 19:38
  • ObservableCollection properties should be read only. The collection itself has change notification, there's no need for its property to have change notification as well. –  Jun 30 '17 at 19:39

2 Answers2

2

Whenever an item is added to your ObservableCollection, you need to add a PropertyChanged event handler to that item and use it to update your totals.

Whenever an item is removed from your ObservableCollection, you need to remove that handler.

Whenever an item is replaced in your ObservableCollection, you need to remove the handler from the old item and add a handler to the new item.

This is usually implemented inside the CollectionChanged handler using a switch statement on the change type enum of the NotifyCollectionChangedEventArgs.

At the moment you are only tracking changes to the collection itself. You need to also monitor changes to the items :)

Other than that, you have no MVVM problem here. Your code is, other than the above, expert-level MVVM. There is no further MVVM way to enhance your code.

hoodaticus
  • 3,772
  • 1
  • 18
  • 28
-1

Take a look to Dependency Properties Tutorial in C#. Very useful for your propose.

Allows you to bind models and views in a bidirectional way.

MarcSanchez
  • 101
  • 3
  • Do note though that using DependencyProperties rather than INotifyPropertyChanged for your view models is considered kinda weird and also hurts your ability to multithread your view models. – hoodaticus Jun 30 '17 at 19:13