0

I made a simple example playing with MVVM. I have a simple class Person in Models, then a class PersonsViewModel has a list of Person. I'm doing so by install Prism through Nuget and raise the event PropertyChanged in the collection of people.

        private ObservableCollection<Person> people;

        public ObservableCollection<Person> People
        {
            get { return people; }
            set
            {
                people = value;
                this.RaisePropertyChanged("People");
            }
        }

then I bind it to a datagrid

        <DataGrid Grid.Row="1" x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding People}" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="First Name" Binding="{Binding FirstName, Mode=OneWay}"/>
                <DataGridTextColumn Header="Last Name" Binding="{Binding LastName, Mode=OneWay}"/>
                <!--<DataGridTextColumn Header="Sex" Binding="{Binding Sex, Mode=TwoWay}"/>-->
                <DataGridComboBoxColumn Header="Sex" SelectedItemBinding="{Binding Sex, Converter={StaticResource sexTypeConverter}}" ItemsSource="{Binding Source={StaticResource sexType}}"/>
                <DataGridTextColumn Header="Score" Binding="{Binding Score, Mode=TwoWay}"/>
            </DataGrid.Columns>
        </DataGrid>

it works find so far, i can insert or remove line, change the quantity in the UI and reflect to the viewmodel. But the problem is that if I change the quantity in the code, it won't show in the form until I double click into it. here is the screenshot when my app starts, i've added three records with different scores. enter image description here

in my code, I've put a command as below, so basically everytime I run the command it will increase the score by 20 for each person. Which it does. the problem is that after I click the button, the score on the form doesn't change, only when i double click into the field score, I can see the number is actually changed.

        private void IncQty()
        {
            foreach (Person item in this.People)
            {
                item.Score += 20;
            }
        }

I've tried to search online, some say that I should bind the datasource again. But isn't ObservableCollection already implements the INotifyPropertyChanged and my view should talk to my viewmodel everytime something is changed? Also ideally I shouldn't do anything in my ViewModel to manipulate the element in View, it seems to break this rule if I write some code to attach the bind again in viewmodel. Please help. Thanks a lot.

wkSpeaker
  • 11
  • 3

1 Answers1

0

The object contained within your observablecollection needs to implement INotifyPropertyChanged. Without it none of the UI components (and the ObservableCollection itself) can know something has changed and therefore need to refresh.

e.g.:

public abstract class ObservableBase : INotifyPropertyChanged
{
    private PropertyChangedEventHandler _notifyPropertyChanged;

    public event PropertyChangedEventHandler PropertyChanged
    {
        add { this._notifyPropertyChanged = (PropertyChangedEventHandler)Delegate.Combine(this._notifyPropertyChanged, value); }
        remove { this._notifyPropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(this._notifyPropertyChanged, value); }
    }

    protected void OnPropertyChanged(string property)
    {
        if(this._notifyPropertyChanged != null) 
        {
            this._notifyPropertyChanged.Invoke(property);
        }
    }
}

public class Person : ObservableBase
{
    private int _score;
     
    public int Score
    {
        get => this._score;
        set 
        {
            if(this._score != value)
            {
                this._score = value;
                // would be better if mixed with reflection and Expression
                // so you can this.OnPropertyChanged(p => p.Score);
                // as per Rockford Lhotka & CSLA
                this.OnPropertyChanged("Score");
            }
        }
    }
}

As a result when you set the score property, property changed will fire, which should mean the datarow component it is bound to will hear it. Even if not the observable collection listens to see if it's T is also INotifyPropertyChanged and will fire an event of its own letting the UI know that the item has changed. Either way your WPF will now reflect the changed applied to score.

user177933
  • 63
  • 5
  • Thank you for the quick update, forgive me that I have to ask some more questions. I'm trying to use this example to understand the MVVM thoughts. the class Person is just a pure class stays in my model, and then it's my class PersonsViewModel inherit from the BindableBase: ```class PersonsViewModel:BindableBase``` – wkSpeaker Apr 01 '22 at 12:44
  • and for the property ``` public ObservableCollection People { get; set; }``` i define it like this, thought it will imply the INotifyPropertyChanged automatally. – wkSpeaker Apr 01 '22 at 12:48
  • in my example I installed Prism through Nuget. And I thought that I don't have to implement the methods in INotifyPropertyChanged because it's already done in the class BindableBase. – wkSpeaker Apr 01 '22 at 14:04
  • If `PersonsViewModel` is what derives from `BindableBase` and implements `INotifyPropertyChanged` then *that* is the thing you need to put in the `ObservableCollection`, not `Person`. At least it is if you want each grid row to update as each `PersonViewModel` changes. – Joe Apr 01 '22 at 17:18
  • Yes, i've changed my property People (i've updated my code in the post), but still doesn't work. wonder what's missing here... – wkSpeaker Apr 02 '22 at 00:18
  • I think I've found out the reason. make the ObservableCollection notifiable doesn't means all the members in this T is notifiable. I made ObservableCollection People a notifiable property, so it will refect to the datagrid when I insert or delete a record. but to reflect the change to a member such as person.score, I have to either make the class person inherit from Bindablebase or recreate all the members in the viewmodel, if I want to keep the model "pure"... – wkSpeaker Apr 02 '22 at 06:30