Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AutoUpdatingSortedObservableCollection.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 
7 namespace SiliconStudio.Presentation.Collections
8 {
9  /// <summary>
10  /// An observable collection that automatically sorts inserted items using either the default comparer for their type, or a custom provider comparer.
11  /// Insertion and search are both O(log(n)). The method <see cref="SortedObservableCollection{T}.BinarySearch"/> must be used for O(log(n)).
12  /// The items must implement <see cref="INotifyPropertyChanging"/> and <see cref="INotifyPropertyChanged"/>.
13  /// The collection watches for property changes in its items and reorders them accordingly if the changes affect the order.
14  /// </summary>
15  /// <typeparam name="T">The type of item this collection contains. Must be a class that implements <see cref="INotifyPropertyChanging"/> and <see cref="INotifyPropertyChanged"/>.</typeparam>
16  /// <seealso cref="SortedObservableCollection{T}"/>
17  public class AutoUpdatingSortedObservableCollection<T> : SortedObservableCollection<T> where T : class, INotifyPropertyChanging, INotifyPropertyChanged
18  {
19  protected T ChangingItem;
20  protected int ChangingIndex;
21  protected T AddedItem;
22  protected int AddedIndex;
23  private int changeCount;
24 
25  /// <summary>
26  /// Public constructor. A comparer can be provided to compare items. If null, the default comparer will be used (if available).
27  /// If no comparer is provided and no default comparer is available, an <see cref="InvalidOperationException"/> will be thrown when methods requesting comparer are invoked.
28  /// </summary>
29  /// <param name="comparer">The comparer to use to compare items.</param>
31  : base(comparer)
32  {
33  }
34 
35  /// <inheritdoc/>
36  public override string ToString()
37  {
38  return string.Format("{{AutoUpdatingSortedObservableCollection}} Count = {0}", Count);
39  }
40 
41  protected virtual void ItemPropertyChanging(object sender, PropertyChangingEventArgs e)
42  {
43  var item = (T)sender;
44  if (ChangingItem != null && !ReferenceEquals(ChangingItem, item))
45  throw new InvalidOperationException("Multiple items in the collection are changing concurrently.");
46 
47  ++changeCount;
48 
49  ChangingItem = item;
50  ChangingIndex = GetIndex(item, false);
51  AddedItem = null;
52  }
53 
54  protected virtual void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
55  {
56  var item = (T)sender;
57 
58  // An object has been added while a property of an existing object has been modified
59  if (ChangingItem != null && AddedItem != null)
60  throw new InvalidOperationException("PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently.");
61 
62  // The object has been added to the collection during the property change, so it was not preregistered by the ItemPropertyChanging event
63  if (ChangingItem == null && AddedItem != null)
64  {
65  ChangingItem = AddedItem;
66  ChangingIndex = AddedIndex;
67  ++changeCount;
68  }
69 
70  // No object is changing, or a different object is currently changing
71  if (ChangingItem == null || !ReferenceEquals(ChangingItem, item))
72  {
73  throw new InvalidOperationException("PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently.");
74  }
75 
76  --changeCount;
77  if (changeCount == 0)
78  {
79  bool needReorder = (ChangingIndex > 0 && DefaultCompareFunc(Items[ChangingIndex - 1], item) > 0) || (ChangingIndex < Count - 1 && DefaultCompareFunc(item, Items[ChangingIndex + 1]) > 0);
80  if (needReorder)
81  {
82  int newIndex = GetReorderingIndex(item);
83  if (newIndex != ChangingIndex && newIndex != ChangingIndex + 1)
84  {
85  if (newIndex > ChangingIndex)
86  --newIndex;
87 
88  ObservableCollectionMoveItem(ChangingIndex, newIndex);
89  }
90  else
91  ChangingIndex = GetIndex(item, false);
92  }
93  ChangingItem = null;
94  }
95  }
96 
97  protected int GetReorderingIndex(T item)
98  {
99  int imin = 0;
100  int imax = Count - 1;
101  while (imax >= imin)
102  {
103  int imid = (imin + imax) / 2;
104 
105  int comp = DefaultCompareFunc(this[imid], item);
106  if (comp < 0)
107  imin = imid + 1;
108  else if (comp > 0)
109  imax = imid - 1;
110  else
111  {
112  bool found = true;
113  if (imid > 0)
114  {
115  comp = DefaultCompareFunc(this[imid - 1], item);
116  if (comp > 0)
117  {
118  imax = imid - 1;
119  found = false;
120  }
121  }
122  if (imid < Count - 1)
123  {
124  comp = DefaultCompareFunc(this[imid + 1], item);
125  if (comp < 0)
126  {
127  imin = imid + 1;
128  found = false;
129  }
130  }
131  if (found)
132  return imid;
133  }
134  }
135 
136  return imin;
137  }
138 
139  /// <inheritdoc/>
140  protected override void InsertItem(int index, T item)
141  {
142  item.PropertyChanging += ItemPropertyChanging;
143  item.PropertyChanged += ItemPropertyChanged;
144  base.InsertItem(index, item);
145  AddedItem = item;
146  AddedIndex = GetIndex(item, false);
147  }
148 
149  /// <inheritdoc/>
150  protected override void ClearItems()
151  {
152  foreach (var item in Items)
153  {
154  item.PropertyChanging -= ItemPropertyChanging;
155  item.PropertyChanged -= ItemPropertyChanged;
156  }
157  base.ClearItems();
158  }
159 
160  /// <inheritdoc/>
161  protected override void RemoveItem(int index)
162  {
163  var item = Items[index];
164  item.PropertyChanging -= ItemPropertyChanging;
165  item.PropertyChanged -= ItemPropertyChanged;
166  if (ChangingItem == item)
167  {
168  ChangingItem = null;
169  changeCount = 0;
170  }
171  base.RemoveItem(index);
172  }
173  }
174 }
AutoUpdatingSortedObservableCollection(IComparer< T > comparer=null)
Public constructor. A comparer can be provided to compare items. If null, the default comparer will b...