Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Updating Observable Collection

0.00/5 (No votes)
29 Jun 2016 1  
This class based on the ObservableCollection supports updating instead of replacing

Introduction

This class along with an abstract class will the inherit ViewModel used in the collection supports updating data from Models.

Background

I had a situation when data was being updated continuously, and I tried just replacing all items in an ObservableCollection with the new items. This would have worked except that the DataTemplate had a Button, and the Button was not triggering the ICommand Binding. I therefore figured I could create a generic that would take an IEnumerable of Models, check to see if the Model already was in the collection, and would only create new ViewModels in the ObservableCollection for those that current did not exist, updated the data in the ViewModels that had new Model, and only created new ViewModels for the Models that did not currently have a ViewModel. The order of the Models is preserved for the ViewModels. This worked. I have a sample that basically has 4 Models that are updated at 100ms, but one of the Models is not in the update collection. This seems to work great. My case will not normally be deleting and adding ViewModels at the rate in this sample.

Using the Code

The collection item ViewModel is based on the UpdateObservableCollectionViewModel<TModel> generic abstract class. This class has a concrete implementation of the Update method which takes an IEnumerable of TModel. Since this class can be created using the default constructor, a newly created ViewModel will have a null value for the Model. In the Update, if the Model is null, it is assumed that there will be only a single Model passed to the Update method, and this is the Model to use for this instance of the ViewModel. Otherwise the Models passed to the abstract class will be checked to see if one has the same GetHashCode value as the Model already associated with the ViewModel. Obviously, since these Models are all new, the only way that one will match is if the GetHashCode is overridden so that a match can be associated with the Model already associated with the ViewModel. When a new Model is found to be associated with the existng Model, the Model is replaced and the abstract Update method is called. The method returns a bool to indicate that a matching Model was found.

There is also a concrete method that allows comparing the Model associated with the ViewModel with an instance of the Model class.

public abstract class <font face="Courier New">UpdateObservableCollectionViewModel</font>

The generic UpdateObservableCollection inherits from the generic ObservableCollection class, and adds only a single method to the base class:

public class UpdateObservableCollection  <TViewModel, TModel> : ObservableCollection<TViewModel>
 where TViewModel : UpdateCollectionItemViewModel<TModel>, new()
{
 public void Update(IEnumerable<TModel> values)
 {
  if (values == null)
  {
   this.Clear();
   return;
  }
  var viewModels = this.ToArray();
  var models = values.ToArray();
  foreach (var viewModel in viewModels)
  {
   if (viewModel.Update(models))
   {
    this.Remove(viewModel);
   }
  }
  int viewModelCounter = 0;
  viewModels = this.ToArray();

  for (int modelCounter = 0; modelCounter < models.Count(); modelCounter++)
  {
   if (viewModels.Length > viewModelCounter && viewModels[viewModelCounter]
       .MatchModel(models[modelCounter]))
    viewModelCounter++;
   else
   {
    var newViewModel = new TViewModel();
    newViewModel.Update(new[] { models[modelCounter] });
    this.Insert(modelCounter, newViewModel);
   }
  }
 }

This method basically executes the Update method of each ViewModel, and the ViewModel indicates if its Model matches one of the new Models. Those that do not have matching Model are deleted. Then the method goes through the Models and checks for a matching ViewModel, inserting new ViewModels for any missing Models.

The Sample

This is used directly in the base ViewModel:

public UpdateCollection<UpdateCollectionItemViewModel, UpdateCollectionItemViewModel> Collection
{ get; } =    new UpdateCollection<UpdateCollectionItemViewModel, UpdateCollectionItemModel>();

The Model does not have to inherit from any class because the only important thing in the implementation is the implementation of the GetHashCode. The GetHashCode is important because it is used to associate two Models as being the same, and the point is that Models used for updates will have a different address from the original Models. In this case, the Key is what is used to associate two Models together, and to do this, the GetHashCode uses the GetHashCode of the Key:

public class UpdateObservableCollectionSampleModel
{
 public UpdateObservableCollectionSampleModel(string key, double value)
 {
  Key = key;
  Value = value;
 }

 public string Key { get; }
 public double Value { get; }

 public override int GetHashCode()
 {
  return Key.GetHashCode();
 }
}

The UpdateObservableCollectionSampleViewModel in the sample is a class that inherits from the UpdateCollectionItemViewModel:

public class UpdateObservableCollectionSampleViewModel : UpdateObservableCollectionViewModel<UpdateObservableCollectionSampleModel>
{
 public UpdateObservableCollectionSampleViewModel(){}

 public UpdateObservableCollectionSampleViewModel(UpdateObservableCollectionSampleModel model)
 {
  Model = model;
  Update();
 }

 public string Key { get { return _key; } set { Set(ref _key, value); } }
 private string _key;

 public double Value { get { return _value; } set { Set(ref _value, value); } }
 private double _value;

 protected override void Update()
 {
  Key = Model.Key;
  Value = Model.Value;
 }
}

In this sample you can click on the top buttons and note how seldom the click is recognized using a standard ObservableCollection. On the bottom buttons, the click is a lot more reliable. If the ViewModels are seldom replaced, then it will be even more reliable. Currently using an update rate of 250 ms. On a different computer, reliabilities could be different, and may want to change this value. 

History

  • 2016/06/29: Initial version
  • 2016/06/20: Some cleanup

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here