0

What should I do to make the WPF DataGrid display changes in its ObservableCollection's items when they are changed in code?

<DataGrid HorizontalAlignment="Left" Height="100" Margin="54,25,0,0" VerticalAlignment="Top" Width="264" 
    IsSynchronizedWithCurrentItem="True" AutoGenerateColumns="False" 
    ItemsSource="{Binding DataSource}" SelectedItem="{Binding SelectedPerson}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding FirstName}" />
        <DataGridTextColumn Binding="{Binding LastName}" />
    </DataGrid.Columns>
</DataGrid>

I update SelectedPerson.FirstName in code and the setter of DataSource executes. The current DataSource.FirstName has been updated but the DataGrid does not display the change. If I click on a column header the DataGrid refreshes and shows the changed data. How can I make it refresh promptly when I am using MVVM and I don't have a reference to the DataGrid?

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class VM : Notifier
{
    private ObservableCollection<Person> dataSource;
    public ObservableCollection<Person> DataSource
    {
        get { return dataSource; }
        set
        {
            dataSource = value;
            OnPropertyChanged("DataSource");
        }
    }

    private Person selectedPerson;
    public Person SelectedPerson
    {
        get { return selectedPerson; }
        set
        {
            selectedPerson = value;
            OnPropertyChanged("SelectedPerson");
        }
    }

    private string string1;
    public string String1
    {
        get { return string1; }
        set
        {
            string1 = value;
            OnPropertyChanged(String1);
        }
    }

    public VM()
    {
        String1 = "abc";

        DataSource = new ObservableCollection<Person>();
        DataSource.Add(new Person { FirstName = "Alpha", LastName = "Apple" });
        DataSource.Add(new Person { FirstName = "Beta", LastName = "Banana" });
        DataSource.Add(new Person { FirstName = "Charlie", LastName = "Cucumber" });
    }

    public ICommand SetSelectedCommand => new RelayCommandBase(SetSelected);
    private void SetSelected(object parameter = null)
    {
        SelectedPerson.FirstName = String1;
        SelectedPerson = SelectedPerson;    // force setter to run
        DataSource = DataSource;            // force ObservableCollection setter to run
    }
}
Moonling
  • 59
  • 2
  • 9

2 Answers2

2

The Person class should implement the INotifyPropertyChanged interface:

public class Person : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            NotifyPropertyChanged();
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            NotifyPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • That works. Thanks. How should I implement the same when the Person class is generated by EntityFramework? – Moonling Jan 19 '18 at 16:30
  • Please ask another question if you have another issue. But either don't bind to the generated entity classes - create your own classes - or modify them: https://stackoverflow.com/questions/11010718/how-to-get-property-change-notifications-with-ef-4-x-dbcontext-generator/12192358#12192358 – mm8 Jan 19 '18 at 16:34
0

Your changes are not getting reflected as the item that is getting updated (Person) DOES NOT raise change notifications to update the UI. To do so, you need to update the Person class to Implement the interface INotifyPropertyChanged in the class Person, the same way you implemented in the viewmodel, and this should work.

public class Person:Notifier
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged(nameof(this.FirstName));
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged(nameof(this.LastName));
        }
    }
}

One more thing. If supported in your version of .NET Framework, you can use the C# nameof to get the property name in string instead of hard-coding the name. This helps when you refactor/rename the properties.

undefined
  • 142
  • 10
  • Thanks @JCB. Can you advise me for the situation where Person class is generated by Entity Framework? – Moonling Jan 19 '18 at 16:35
  • Unfortunately there is no straight forward way to implement the same. The proper way to do this is create a mapping between your Model and the ViewModel. As such you need to create a ViewModel for Person for e.g PersonViewModel and pass the DTO Person to it while creating, so that whenever a property of the person ViewModel is updated, it updates the DTO property as well in the setter. This is explained somewhat here: https://stackoverflow.com/a/14870073/2147335 https://stackoverflow.com/a/14870145/2147335 – undefined Jan 19 '18 at 16:55
  • I have written a simple form to change EF-generated POCO scalar entity properties to notifying ones and I will use this from now on to alter my T4 generated classes. – Moonling Jan 23 '18 at 09:55