bovender framework
C# framework that implements MVVM and more
ViewModelCollection.cs
1 /* ViewModelCollection.cs
2  * part of Daniel's XL Toolbox NG
3  *
4  * Copyright 2014-2018 Daniel Kraus
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 using System;
19 using System.Collections.Generic;
20 using System.Collections.ObjectModel;
21 using System.Collections.Specialized;
22 using System.Linq;
23 using System.Text;
25 using System.ComponentModel;
26 using System.Collections;
27 
28 namespace Bovender.Mvvm.ViewModels
29 {
36  public abstract class ViewModelCollection<TModel, TViewModel>
37  : ObservableCollection<TViewModel> where TViewModel : ViewModelBase
38  {
39  #region Public properties
40 
41  public int CountSelected
42  {
43  get { return _countSelected; }
44  set
45  {
46  _countSelected = value;
47  OnPropertyChanged(new PropertyChangedEventArgs("CountSelected"));
48  }
49  }
50 
51  public TViewModel LastSelected
52  {
53  get { return _lastSelected; }
54  set
55  {
56  _lastSelected = value;
57  OnPropertyChanged(new PropertyChangedEventArgs("LastSelected"));
58  }
59  }
60 
61  #endregion
62 
63  #region Public methods
64 
71  public void RemoveSelected()
72  {
73  var selectedItems = Items.Where(vm => vm.IsSelected).ToList();
74  if (selectedItems.Count > 0)
75  {
76  // Use Items.Remove() which does not trigger the CollectionChanged event.
77  selectedItems.ForEach(i => Items.Remove(i));
78  CountSelected = 0;
79  LastSelected = null;
80  // OnPropertyChanged(new PropertyChangedEventArgs("Count"));
81  // OnPropertyChanged(new PropertyChangedEventArgs("Items[]"));
82  OnCollectionChanged(
83  new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)
84  );
85  }
86  }
87 
88  #endregion
89 
90  #region Events
91 
95  public event EventHandler<PropertyChangedEventArgs> ViewModelPropertyChanged;
96 
97  #endregion
98 
99  #region Abstract methods
100 
101  protected abstract TViewModel CreateViewModel(TModel model);
102 
103  #endregion
104 
105  #region Constructor
106 
107  public ViewModelCollection(ObservableCollection<TModel> modelCollection)
108  {
109  _modelCollection = modelCollection;
110  // The BuildViewModelCollection adds the event handlers
111  // when done, so there is no need to add the event handlers
112  // via SynchronizeOn() in the constructor. Avoid adding the
113  // handlers twice...
114  SynchronizeOn();
115  BuildViewModelCollection();
116  }
117 
118  #endregion
119 
120  #region Event handlers
121 
127  void ViewModelCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
128  {
129  SynchronizeOff();
130  switch (e.Action)
131  {
132  case NotifyCollectionChangedAction.Add:
133  DoAddModelObjects(e.NewItems);
134  break;
135  case NotifyCollectionChangedAction.Remove:
136  DoRemoveModelObjects(e.OldItems);
137  break;
138  case NotifyCollectionChangedAction.Move:
139  // Don't do anything if items are moved.
140  break;
141  case NotifyCollectionChangedAction.Replace:
142  DoRemoveModelObjects(e.OldItems);
143  DoAddModelObjects(e.NewItems);
144  break;
145  case NotifyCollectionChangedAction.Reset:
146  BuildModelCollection();
147  break;
148  }
149  SynchronizeOn();
150  }
151 
157  void _modelCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
158  {
159  SynchronizeOff();
160  switch (e.Action)
161  {
162  case NotifyCollectionChangedAction.Add:
163  DoAddViewModelObjects(e.NewItems);
164  break;
165  case NotifyCollectionChangedAction.Remove:
166  DoRemoveViewModelObjects(e.OldItems);
167  break;
168  case NotifyCollectionChangedAction.Move:
169  // No need to synchronize, we don't care about order
170  break;
171  case NotifyCollectionChangedAction.Replace:
172  DoRemoveViewModelObjects(e.OldItems);
173  DoAddViewModelObjects(e.NewItems);
174  break;
175  case NotifyCollectionChangedAction.Reset:
176  BuildViewModelCollection();
177  break;
178  }
179  SynchronizeOn();
180  }
181 
182  private void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
183  {
184  if (e.PropertyName == "IsSelected")
185  {
186  if (((TViewModel)sender).IsSelected)
187  {
188  LastSelected = sender as TViewModel;
189  CountSelected++;
190  }
191  else
192  {
193  LastSelected = null;
194  CountSelected--;
195  }
196  }
197  OnViewModelPropertyChanged(sender, e);
198  }
199 
200  #endregion
201 
202  #region Private methods
203 
208  protected void SynchronizeOn()
209  {
210  _synchronizing++;
211  if (_synchronizing == 1)
212  {
213  _modelCollection.CollectionChanged += _modelCollection_CollectionChanged;
214  this.CollectionChanged += ViewModelCollection_CollectionChanged;
215  }
216  }
217 
222  protected void SynchronizeOff()
223  {
224  _synchronizing--;
225  if (_synchronizing == 0)
226  {
227  _modelCollection.CollectionChanged -= _modelCollection_CollectionChanged;
228  this.CollectionChanged -= ViewModelCollection_CollectionChanged;
229  }
230  }
231 
232  protected void BuildViewModelCollection()
233  {
234  try
235  {
236  SynchronizeOff();
237  this.Clear();
238  foreach (TModel m in _modelCollection)
239  {
240  AddViewModelWithEvent(m);
241  }
242  }
243  finally
244  {
245  SynchronizeOn();
246  }
247  }
248 
249  private void DoAddViewModelObjects(IList modelObjects)
250  {
251  foreach (TModel m in modelObjects)
252  {
253  AddViewModelWithEvent(m);
254  }
255  }
256 
257  private void DoRemoveViewModelObjects(IList modelObjects)
258  {
259  foreach (TModel m in modelObjects)
260  {
261  Items.Remove(
262  Items.FirstOrDefault(
263  (TViewModel vm) => vm.IsViewModelOf(m)
264  )
265  );
266  }
267  }
268 
269  private void BuildModelCollection()
270  {
271  try
272  {
273  SynchronizeOff();
274  _modelCollection.Clear();
275  foreach (TViewModel vm in Items)
276  {
277  _modelCollection.Add((TModel)vm.RevealModelObject());
278  }
279  }
280  finally
281  {
282  SynchronizeOn();
283  }
284  }
285 
286  private void DoAddModelObjects(IList viewModelObjects)
287  {
288  foreach (TViewModel vm in viewModelObjects)
289  {
290  vm.PropertyChanged += viewModel_PropertyChanged;
291  _modelCollection.Add((TModel)vm.RevealModelObject());
292  }
293  }
294 
295  private void DoRemoveModelObjects(IList viewModelObjects)
296  {
297  foreach (TViewModel vm in viewModelObjects)
298  {
299  vm.PropertyChanged -= viewModel_PropertyChanged;
300  _modelCollection.Remove((TModel)vm.RevealModelObject());
301  }
302  }
303 
304  private void AddViewModelWithEvent(TModel model)
305  {
306  TViewModel viewModel = CreateViewModel(model);
307  viewModel.PropertyChanged += viewModel_PropertyChanged;
308  Add(viewModel);
309  }
310 
311  private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs args)
312  {
313  if (ViewModelPropertyChanged != null)
314  {
315  ViewModelPropertyChanged(sender, args);
316  }
317  }
318 
319  #endregion
320 
321  #region Private fields
322 
323  readonly ObservableCollection<TModel> _modelCollection;
324  private int _countSelected;
325  private int _synchronizing;
326  private TViewModel _lastSelected;
327 
328  #endregion
329  }
330 }
EventHandler< PropertyChangedEventArgs > ViewModelPropertyChanged
Relays property-changed events from the view models in the collection.
Collection of view models that automatically syncs with an associated collection of model objects...
void SynchronizeOn()
Turns on synchronization of the view model collection and the model collection by adding appropriate ...
void RemoveSelected()
Removes all selected view models from the collection.
void SynchronizeOff()
Turns off synchronization of the view model collection and the model collection by removing the event...