Search code examples
c#wpfmvvmobservablecollectionbindinglist

Update object property after property in BindingList is changed


I'm very new to WPF and MVVM and it frustrates me a lot at the moment. My current problem is:

I have the following XAML:

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext>

<WrapPanel Orientation="Horizontal">
    <ComboBox ItemsSource="{Binding StreetList}" SelectedItem="{Binding SelectedStreet}" DisplayMemberPath="StreetName" IsSynchronizedWithCurrentItem="True"/>
    <DataGrid ItemsSource="{Binding SelectedStreet.HouseList}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="YearBuilt" Binding="{Binding YearBuilt}" />
            <DataGridTextColumn Header="Price" Binding="{Binding Price}" />
            <DataGridTextColumn Header="#Rooms" Binding="{Binding Rooms}" />
        </DataGrid.Columns>
    </DataGrid>
    <TextBox Text="{Binding AvgPricePerRoom, Mode=OneWay}" />
</WrapPanel>

where:

public class MainViewModel : ViewModelBase
{
    public BindingList<StreetViewModel> StreetList { get { return _streetList; } }
    private BindingList<StreetViewModel> _streetList;

    public StreetViewModel SelectedStreet
    {
        get { return _selectedStreet; }
        set { _selectedStreet = (StreetViewModel)value.Clone(); RaisePropertyChanged(); RaisePropertyChanged(nameof(AvgPricePerRoom)); }
    }
    private StreetViewModel _selectedStreet;

    public double AvgPricePerRoom
    {
        get
        {
            return
              _selectedStreet.HouseList[0].Price / _selectedStreet.HouseList[0].Rooms / 3 +
              _selectedStreet.HouseList[1].Price / _selectedStreet.HouseList[1].Rooms / 3 +
              _selectedStreet.HouseList[2].Price / _selectedStreet.HouseList[2].Rooms / 3;
        }
    }

    public MainViewModel()
    {
        _streetList = new BindingList<StreetViewModel>();
        fill_HouseGroupList();
    }

    void fill_HouseGroupList() { ... }
}

and (EDITED):

public class StreetViewModel : ViewModelBase, ICloneable
{
    public string StreetName
    {
        get { return _streetName; }
        set { _streetName = value; RaisePropertyChanged(); }
    }
    private string _streetName;


    private BindingList<HouseViewModel> _houseList;

    private void HouseList_ListChanged(object sender, ListChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public StreetViewModel() { }

    public StreetViewModel(string streetName) : this()
    {
        _streetName = streetName;

        _houseList = new BindingList<HouseViewModel>();

        HouseList.ListChanged += HouseList_ListChanged;
    }

    public BindingList<HouseViewModel> HouseList
    {
        get
        {
            if(_houseList.Count == 0)
            {
                switch (_streetName)
                {
                    case "Main street":
                        _houseList.Add(new HouseViewModel(new House(2015, 600, 9)));
                        _houseList.Add(new HouseViewModel(new House(1929, 20, 4)));
                        _houseList.Add(new HouseViewModel(new House(1969, 30, 6)));
                        break;
                    case "School street":
                        _houseList.Add(new HouseViewModel(new House(2017, 50, 10)));
                        _houseList.Add(new HouseViewModel(new House(1930, 10, 5)));
                        _houseList.Add(new HouseViewModel(new House(1970, 20, 7)));
                        break;
                    case "Garden street":
                        _houseList.Add(new HouseViewModel(new House(2014, 1, 15)));
                        _houseList.Add(new HouseViewModel(new House(1935, 20, 11)));
                        _houseList.Add(new HouseViewModel(new House(1978, 20, 9)));
                        break;
                }
            }
            return _houseList;
        }
    }

    public object Clone()
    {
        var houseGroupCopy = new StreetViewModel(this._streetName);

        houseGroupCopy._houseList = new BindingList<HouseViewModel>();
        foreach (var house in _houseList)
        {
            houseGroupCopy._houseList.Add(new HouseViewModel(new House(house.YearBuilt, house.Price, house.Rooms)));
        }

        return houseGroupCopy;
    }
}

plus:

public class HouseViewModel : ViewModelBase
{
    public House House
    {
        get { return _house; }
        set { _house = value;  RaisePropertyChanged(); }
    }
    private House _house;

    public int YearBuilt
    {
        get { return _house.YearBuilt; }
        set { _house.YearBuilt = value; RaisePropertyChanged(); }
    }

    public double Price
    {
        get { return _house.Price; }
        set { _house.Price = value; RaisePropertyChanged(); }
    }

    public int Rooms
    {
        get { return _house.Rooms; }
        set { _house.Rooms = value; RaisePropertyChanged(); }
    }

    public HouseViewModel(House house)
    {
        _house = house;
    }
}

... so in the DataGrid of my view I can edit the properties of the House items in my HouseList which is a BindingList. How can I make the property AvgPricePerRoom - which is displayed in the TextBox - aware of the changes in the DataGrid, so that the updated AvgPricePerRoom is shown upon changes in the Grid? I have used the BindingList because I read that unlike ObservableCollection it raises an event also when the properties of its items change (performance problems aside), but how do I handle this event and notify my AvgPricePerRoom in this example?

(Not all of the code above is important for my problem, and I am sorry for this...)


Solution

  • When binding collections, there are 3 kinds of Change notiication you must take care off:

    1. Notifications for adding/removes from the list. This is the only Notification ObservableCollection actually takes care off.
    2. Notifications for the property exposing the ObservableCollection (StreetList in your example). OC are very bad at large scale changes (no AddRange function), so it is often better to create a new list in code, only exposing it as the last step.
    3. Change Notification on each property of StreetViewModel.