1

My DataGrid control's bound to an instance of a ListCollectionView in the view model and in its getter, I check for the underlying property AllThings. This far, the populating of the backing field _allThings's been performed in the constructor of the view model and since the data in the DB only changed over night, it was fully sufficient.

However, as of now, there'll be new instances of Thing created and shot into the DB. Supposing that a user clicks a button that fires an event handled in the method UpdateData(Object, RoutedEventArgs), what's the appropriate approach to re-rendering the data grid?

Is it necessary for the property AllThings to be of type ObservableCollection or will it be sufficient to go with IEnumerable? (In the future I might want to update the grid if something relevant changes in the DB at the moment it's not of any concern.)

The reason for me being so curious of the proper approach is the difference of where the data comes from. Usually, the data comes from a binding on the same control that's causing the event of new data available. Here, the control (button) will notify that there's some new data but it comes from another source, namely the DB. I'm not certain if one should handle it differently and, if so, how.

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438

2 Answers2

1

Change notification in WPF is based on INotifyPropertyChanged.

For the consumer (your DataGrid) it is just an event (PropertyChanged) fired when some changes happen. The binding engine will react accordingly refreshing the DataGrid.

You must implement INotifyPropertyChanged in your ViewModel, and fire the event as needed (ie after the user press the "reload data" button")

public class Car
{
  public string Model;
  public int Speed;
}

class MyViewModel: INotifyPropertyChanged
{
  List<Car> cars;

  public List<Car> Cars { get {return cars;}}

  // this method is invoked from GUI, ie from an event handler or a Command of a "Reload" button
  public void ReloadData()
  {
    // do something to actually refresh cars

    // notify GUI something changed
    InternalPropertyChanged("Cars");
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected void InternalPropertyChanged(string name)
  {
    if (PropertyChanged != null)
      PropertyChanged(this, new PropertyChangedEventArgs(name));
  }
}

This way the whole list is refreshed, which as Weston noted, resets grid selection and position.

An ObservableCollection goes a step further, detecting single elements insertion or removal and allowing to refresh them only. But it does not detect changes in existing elements (ie if Speed change in existing Car).

To detect and refresh existing elements changes you have to implment INotifyPropertyChanged on the single elements (ie on class Car).

Also notice that ObservableCollection adds nothing if your DB query create a new list from scratch (something all DB API usually do).

You should get the new list from the database, compare it with the existing one and call Add/Remove on the ObservableCollection (which probably is not worth it)

corradolab
  • 718
  • 3
  • 16
  • First off +1 for a nice sample code. Just to verify if I got you correctly - the method *InternalPropertyChanged(String)* is **not** the one that implements the interface *INotifyPropertyChanged* in the class *MyViewModel*, right? I'm asking that because I don't see the actual implementation, although the public event *PropertyChanged* is there. Or is it, perhaps, fine to call the default *OnPropertyChanged(String)* from the public *ReloadData()*? – Konrad Viltersten Feb 04 '15 at 14:57
  • Correct. INotifyPropertyChanged is just the event PropertyChanged. But you have to fire it sooner or later :) You could do it directly from ReloadData, but would have to check it's not null and pack the parameter (repetitive stuff moved to InternalPropertyChanged). – corradolab Feb 04 '15 at 16:15
0

It is sufficient to expose IEnumerable on the public interface. You don't want to expose more than you need to and exposing ObservableCollection would allow external classes to modify the collection.

public class Model
{
    private readonly ObservableCollection<Thing> _allThings = new ObservableCollection<Thing>();

    public IEnumerable<Thing> AllThings { get { return _allThings; }}
 }

Note that I do not allow its reference to be changed, (it is readonly) If it's reference were changed (with a notify) it would cause a complete refresh of the grid. The user will loose their place, and any selection and the grid will jump to the top.

Binding to AllThings will just work, there is no more work required to notify the view of changes.

Aside: One notable disadvantage of ObservableCollection over List say, is you cannot AddRange, but see this question for solution to that: ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

Community
  • 1
  • 1
weston
  • 54,145
  • 21
  • 145
  • 203
  • Great to have **that** stated, thanks! Now, what baffles me is that when I set the property *AllThings* (equivalent to setting *_allThings*, while over-exposing the observable collection during testing) to e.g. *null* in the event handler, it seems that the data bound grid doesn't react to that change. Any suggestion on how to trouble-shoot it? – Konrad Viltersten Feb 04 '15 at 11:07
  • I'm not sure what you mean. Please post code. My example has readonly property and backing field, so there is no set to worry about. If you have a settable property then you must notify property changed. – weston Feb 04 '15 at 12:34