Introduction
This class along with an abstract
class will the inherit ViewModel
used in the collection supports updating data from Model
s.
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 Model
s, 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 Model
s that did not currently have a ViewModel
. The order of the Model
s is preserved for the ViewModels
. This worked. I have a sample that basically has 4 Model
s that are updated at 100ms, but one of the Model
s 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 Model
s 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 Model
s 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 Model
s. Those that do not have matching Model
are deleted. Then the method goes through the Model
s and checks for a matching ViewModel
, inserting new ViewModels
for any missing Model
s.
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 Model
s as being the same, and the point is that Model
s used for updates will have a different address from the original Model
s. In this case, the Key
is what is used to associate two Model
s 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 ViewModel
s 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