Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF
Tip/Trick

WPF Updating Observable Collection

Rate me:
Please Sign up or sign in to vote.
4.92/5 (8 votes)
29 Jun 2016CPOL3 min read 24.3K   234   10   2
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.

C#
public abstract class <font face="Courier New">UpdateObservableCollectionViewModel</font><TModel> : NotifyPropertyChanged
{
 protected TModel Model;
 public bool Update(IEnumerable<TModel> models)
 {
  if (Model == null)
  {
   if (models?.Count() == 1)
   {
    Model = models.First();
    Update();
    return false;
   }
   else
    throw new InvalidEnumArgumentException
     ("IUpdateCollectionViewModel class the existing model was null but
       single new model not passed to class");
  }
  else
  {
   var matchModel = models.FirstOrDefault(i => i.GetHashCode() == Model.GetHashCode());
   if (matchModel == null) return true;
   Model = matchModel;
   Update();
   return false;
  }
 }

 protected abstract void Update();

 public bool MatchModel(TModel model)
 {
  return Model.GetHashCode() == model.GetHashCode();
 }
}

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

C#
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:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
GeneralWrong usage of getHashCode Pin
Member 368675329-Jun-16 10:01
Member 368675329-Jun-16 10:01 
AnswerRe: Wrong usage of getHashCode Pin
Clifford Nelson29-Jun-16 11:47
Clifford Nelson29-Jun-16 11:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.