├── ICommand.cs ├── ICommand.cs.meta ├── INotifyCollectionChanged.cs ├── INotifyCollectionChanged.cs.meta ├── NotifyCollectionChangedEventArgs.cs ├── NotifyCollectionChangedEventArgs.cs.meta ├── ObservableCollection.cs ├── ObservableCollection.cs.meta └── README.md /ICommand.cs: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // Copyright (C) Microsoft Corporation. All rights reserved. 4 | // 5 | //--------------------------------------------------------------------------- 6 | 7 | // Adapted for Unity 8 | 9 | #if !NETFX_CORE 10 | 11 | namespace System.Windows.Input 12 | { 13 | /// 14 | /// An interface that allows an application author to define a method to be invoked. 15 | /// 16 | public interface ICommand 17 | { 18 | /// 19 | /// Raised when the ability of the command to execute has changed. 20 | /// 21 | event EventHandler CanExecuteChanged; 22 | 23 | /// 24 | /// Returns whether the command can be executed. 25 | /// 26 | /// A parameter that may be used in executing the command. This parameter may be ignored by some implementations. 27 | /// true if the command can be executed with the given parameter and current state. false otherwise. 28 | bool CanExecute(object parameter); 29 | 30 | /// 31 | /// Defines the method that should be executed when the command is executed. 32 | /// 33 | /// A parameter that may be used in executing the command. This parameter may be ignored by some implementations. 34 | void Execute(object parameter); 35 | } 36 | } 37 | 38 | #endif -------------------------------------------------------------------------------- /ICommand.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 286c9c0f4dda27b4f8940957677e6ec0 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /INotifyCollectionChanged.cs: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // 4 | // Copyright (C) 2003 by Microsoft Corporation. All rights reserved. 5 | // 6 | // 7 | // 8 | // Description: Allows collections to notify listeners of dynamic updates. 9 | // 10 | // See spec at http://avalon/connecteddata/Specs/INotifyCollectionChanged.mht 11 | // 12 | //--------------------------------------------------------------------------- 13 | 14 | // Adapted for Unity 15 | 16 | #if !NETFX_CORE 17 | 18 | namespace System.Collections.Specialized 19 | { 20 | /// 21 | /// A collection implementing this interface will notify listeners of dynamic changes, 22 | /// e.g. when items get added and removed or the whole list is refreshed. 23 | /// 24 | public interface INotifyCollectionChanged 25 | { 26 | /// 27 | /// Occurs when the collection changes, either by adding or removing an item. 28 | /// 29 | /// 30 | /// The event handler receives an argument of type 31 | /// 32 | /// containing data related to this event. 33 | /// 34 | event NotifyCollectionChangedEventHandler CollectionChanged; 35 | } 36 | } 37 | 38 | #endif -------------------------------------------------------------------------------- /INotifyCollectionChanged.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 12a97e7948c8d4342925e0635dcac469 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /NotifyCollectionChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // 4 | // Copyright (C) Microsoft Corporation. All rights reserved. 5 | // 6 | // 7 | // Description: NotifyCollectionChanged event arguments 8 | // 9 | // Specs: http://avalon/connecteddata/Specs/INotifyCollectionChanged.mht 10 | // 11 | //--------------------------------------------------------------------------- 12 | 13 | // Adapted for Unity 14 | 15 | #if !NETFX_CORE 16 | 17 | namespace System.Collections.Specialized 18 | { 19 | /// 20 | /// This enum describes the action that caused a CollectionChanged event. 21 | /// 22 | public enum NotifyCollectionChangedAction 23 | { 24 | /// One or more items were added to the collection. 25 | Add, 26 | 27 | /// One or more items were removed from the collection. 28 | Remove, 29 | 30 | /// One or more items were replaced in the collection. 31 | Replace, 32 | 33 | /// One or more items were moved within the collection. 34 | Move, 35 | 36 | /// The contents of the collection changed dramatically. 37 | Reset, 38 | } 39 | 40 | /// 41 | /// Arguments for the CollectionChanged event. 42 | /// A collection that supports INotifyCollectionChangedThis raises this event 43 | /// whenever an item is added or removed, or when the contents of the collection 44 | /// changes dramatically. 45 | /// 46 | public class NotifyCollectionChangedEventArgs : EventArgs 47 | { 48 | //------------------------------------------------------ 49 | // 50 | // Constructors 51 | // 52 | //------------------------------------------------------ 53 | 54 | /// 55 | /// Construct a NotifyCollectionChangedEventArgs that describes a reset change. 56 | /// 57 | /// The action that caused the event (must be Reset). 58 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action) 59 | { 60 | if (action != NotifyCollectionChangedAction.Reset) 61 | throw new ArgumentException("This constructor can only be used with the Reset action.", "action"); 62 | 63 | InitializeAdd(action, null, -1); 64 | } 65 | 66 | /// 67 | /// Construct a NotifyCollectionChangedEventArgs that describes a one-item change. 68 | /// 69 | /// The action that caused the event; can only be Reset, Add or Remove action. 70 | /// The item affected by the change. 71 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem) 72 | { 73 | if ((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove) 74 | && (action != NotifyCollectionChangedAction.Reset)) 75 | throw new ArgumentException("This constructor can only be used with the Reset, Add, or Remove actions.", "action"); 76 | 77 | if (action == NotifyCollectionChangedAction.Reset) 78 | { 79 | if (changedItem != null) 80 | throw new ArgumentException("This constructor can only be used with the Reset action if changedItem is null", "action"); 81 | 82 | InitializeAdd(action, null, -1); 83 | } 84 | else 85 | { 86 | InitializeAddOrRemove(action, new object[] { changedItem }, -1); 87 | } 88 | } 89 | 90 | /// 91 | /// Construct a NotifyCollectionChangedEventArgs that describes a one-item change. 92 | /// 93 | /// The action that caused the event. 94 | /// The item affected by the change. 95 | /// The index where the change occurred. 96 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index) 97 | { 98 | if ((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove) 99 | && (action != NotifyCollectionChangedAction.Reset)) 100 | throw new ArgumentException("This constructor can only be used with the Reset, Add, or Remove actions.", "action"); 101 | 102 | if (action == NotifyCollectionChangedAction.Reset) 103 | { 104 | if (changedItem != null) 105 | throw new ArgumentException("This constructor can only be used with the Reset action if changedItem is null", "action"); 106 | if (index != -1) 107 | throw new ArgumentException("This constructor can only be used with the Reset action if index is -1", "action"); 108 | 109 | InitializeAdd(action, null, -1); 110 | } 111 | else 112 | { 113 | InitializeAddOrRemove(action, new object[] { changedItem }, index); 114 | } 115 | } 116 | 117 | /// 118 | /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item change. 119 | /// 120 | /// The action that caused the event. 121 | /// The items affected by the change. 122 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems) 123 | { 124 | if ((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove) 125 | && (action != NotifyCollectionChangedAction.Reset)) 126 | throw new ArgumentException("This constructor can only be used with the Reset, Add, or Remove actions.", "action"); 127 | 128 | if (action == NotifyCollectionChangedAction.Reset) 129 | { 130 | if (changedItems != null) 131 | throw new ArgumentException("This constructor can only be used with the Reset action if changedItem is null", "action"); 132 | 133 | InitializeAdd(action, null, -1); 134 | } 135 | else 136 | { 137 | if (changedItems == null) 138 | throw new ArgumentNullException("changedItems"); 139 | 140 | InitializeAddOrRemove(action, changedItems, -1); 141 | } 142 | } 143 | 144 | /// 145 | /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item change (or a reset). 146 | /// 147 | /// The action that caused the event. 148 | /// The items affected by the change. 149 | /// The index where the change occurred. 150 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int startingIndex) 151 | { 152 | if ((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove) 153 | && (action != NotifyCollectionChangedAction.Reset)) 154 | throw new ArgumentException("This constructor can only be used with the Reset, Add, or Remove actions.", "action"); 155 | 156 | if (action == NotifyCollectionChangedAction.Reset) 157 | { 158 | if (changedItems != null) 159 | throw new ArgumentException("This constructor can only be used with the Reset action if changedItem is null", "action"); 160 | if (startingIndex != -1) 161 | throw new ArgumentException("This constructor can only be used with the Reset action if index is -1", "action"); 162 | 163 | InitializeAdd(action, null, -1); 164 | } 165 | else 166 | { 167 | if (changedItems == null) 168 | throw new ArgumentNullException("changedItems"); 169 | if (startingIndex < -1) 170 | throw new ArgumentException("The value of index must be -1 or greater.", "startingIndex"); 171 | 172 | InitializeAddOrRemove(action, changedItems, startingIndex); 173 | } 174 | } 175 | 176 | /// 177 | /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Replace event. 178 | /// 179 | /// Can only be a Replace action. 180 | /// The new item replacing the original item. 181 | /// The original item that is replaced. 182 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem) 183 | { 184 | if (action != NotifyCollectionChangedAction.Replace) 185 | throw new ArgumentException("This constructor can only be used with the Replace action.", "action"); 186 | 187 | InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, -1, -1); 188 | } 189 | 190 | /// 191 | /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Replace event. 192 | /// 193 | /// Can only be a Replace action. 194 | /// The new item replacing the original item. 195 | /// The original item that is replaced. 196 | /// The index of the item being replaced. 197 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem, int index) 198 | { 199 | if (action != NotifyCollectionChangedAction.Replace) 200 | throw new ArgumentException("This constructor can only be used with the Replace action.", "action"); 201 | 202 | int oldStartingIndex = index; 203 | 204 | InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, index, oldStartingIndex); 205 | } 206 | 207 | /// 208 | /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Replace event. 209 | /// 210 | /// Can only be a Replace action. 211 | /// The new items replacing the original items. 212 | /// The original items that are replaced. 213 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems) 214 | { 215 | if (action != NotifyCollectionChangedAction.Replace) 216 | throw new ArgumentException("This constructor can only be used with the Replace action.", "action"); 217 | if (newItems == null) 218 | throw new ArgumentNullException("newItems"); 219 | if (oldItems == null) 220 | throw new ArgumentNullException("oldItems"); 221 | 222 | InitializeMoveOrReplace(action, newItems, oldItems, -1, -1); 223 | } 224 | 225 | /// 226 | /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Replace event. 227 | /// 228 | /// Can only be a Replace action. 229 | /// The new items replacing the original items. 230 | /// The original items that are replaced. 231 | /// The starting index of the items being replaced. 232 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex) 233 | { 234 | if (action != NotifyCollectionChangedAction.Replace) 235 | throw new ArgumentException("This constructor can only be used with the Replace action.", "action"); 236 | if (newItems == null) 237 | throw new ArgumentNullException("newItems"); 238 | if (oldItems == null) 239 | throw new ArgumentNullException("oldItems"); 240 | 241 | InitializeMoveOrReplace(action, newItems, oldItems, startingIndex, startingIndex); 242 | } 243 | 244 | /// 245 | /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Move event. 246 | /// 247 | /// Can only be a Move action. 248 | /// The item affected by the change. 249 | /// The new index for the changed item. 250 | /// The old index for the changed item. 251 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex) 252 | { 253 | if (action != NotifyCollectionChangedAction.Move) 254 | throw new ArgumentException("This constructor can only be used with the Move action.", "action"); 255 | if (index < 0) 256 | throw new ArgumentException("The value of index must be -1 or greater.", "index"); 257 | 258 | object[] changedItems = new object[] { changedItem }; 259 | InitializeMoveOrReplace(action, changedItems, changedItems, index, oldIndex); 260 | } 261 | 262 | /// 263 | /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Move event. 264 | /// 265 | /// The action that caused the event. 266 | /// The items affected by the change. 267 | /// The new index for the changed items. 268 | /// The old index for the changed items. 269 | public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex) 270 | { 271 | if (action != NotifyCollectionChangedAction.Move) 272 | throw new ArgumentException("This constructor can only be used with the Move action.", "action"); 273 | if (index < 0) 274 | throw new ArgumentException("The value of index must be -1 or greater.", "index"); 275 | 276 | InitializeMoveOrReplace(action, changedItems, changedItems, index, oldIndex); 277 | } 278 | 279 | /// 280 | /// Construct a NotifyCollectionChangedEventArgs with given fields (no validation). Used by WinRT marshaling. 281 | /// 282 | internal NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int newIndex, int oldIndex) 283 | { 284 | _action = action; 285 | _newItems = (newItems == null) ? null : ArrayList.ReadOnly(newItems); 286 | _oldItems = (oldItems == null) ? null : ArrayList.ReadOnly(oldItems); 287 | _newStartingIndex = newIndex; 288 | _oldStartingIndex = oldIndex; 289 | } 290 | 291 | private void InitializeAddOrRemove(NotifyCollectionChangedAction action, IList changedItems, int startingIndex) 292 | { 293 | if (action == NotifyCollectionChangedAction.Add) 294 | InitializeAdd(action, changedItems, startingIndex); 295 | else if (action == NotifyCollectionChangedAction.Remove) 296 | InitializeRemove(action, changedItems, startingIndex); 297 | else 298 | throw new ArgumentException("This method must be used with the Add or Remove action.", "action"); 299 | } 300 | 301 | private void InitializeAdd(NotifyCollectionChangedAction action, IList newItems, int newStartingIndex) 302 | { 303 | _action = action; 304 | _newItems = (newItems == null) ? null : ArrayList.ReadOnly(newItems); 305 | _newStartingIndex = newStartingIndex; 306 | } 307 | 308 | private void InitializeRemove(NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex) 309 | { 310 | _action = action; 311 | _oldItems = (oldItems == null) ? null : ArrayList.ReadOnly(oldItems); 312 | _oldStartingIndex = oldStartingIndex; 313 | } 314 | 315 | private void InitializeMoveOrReplace(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex, int oldStartingIndex) 316 | { 317 | InitializeAdd(action, newItems, startingIndex); 318 | InitializeRemove(action, oldItems, oldStartingIndex); 319 | } 320 | 321 | //------------------------------------------------------ 322 | // 323 | // Public Properties 324 | // 325 | //------------------------------------------------------ 326 | 327 | /// 328 | /// The action that caused the event. 329 | /// 330 | public NotifyCollectionChangedAction Action 331 | { 332 | get { return _action; } 333 | } 334 | 335 | /// 336 | /// The items affected by the change. 337 | /// 338 | public IList NewItems 339 | { 340 | get { return _newItems; } 341 | } 342 | 343 | /// 344 | /// The old items affected by the change (for Replace events). 345 | /// 346 | public IList OldItems 347 | { 348 | get { return _oldItems; } 349 | } 350 | 351 | /// 352 | /// The index where the change occurred. 353 | /// 354 | public int NewStartingIndex 355 | { 356 | get { return _newStartingIndex; } 357 | } 358 | 359 | /// 360 | /// The old index where the change occurred (for Move events). 361 | /// 362 | public int OldStartingIndex 363 | { 364 | get { return _oldStartingIndex; } 365 | } 366 | 367 | //------------------------------------------------------ 368 | // 369 | // Private Fields 370 | // 371 | //------------------------------------------------------ 372 | 373 | private NotifyCollectionChangedAction _action; 374 | private IList _newItems, _oldItems; 375 | private int _newStartingIndex = -1; 376 | private int _oldStartingIndex = -1; 377 | } 378 | 379 | /// 380 | /// The delegate to use for handlers that receive the CollectionChanged event. 381 | /// 382 | public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e); 383 | } 384 | 385 | #endif -------------------------------------------------------------------------------- /NotifyCollectionChangedEventArgs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5323829f4b4fac94bb590206a59c859e 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /ObservableCollection.cs: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // 4 | // Copyright (C) 2003 by Microsoft Corporation. All rights reserved. 5 | // 6 | // 7 | // 8 | // Description: Implementation of an Collection implementing INotifyCollectionChanged 9 | // to notify listeners of dynamic changes of the list. 10 | // 11 | // See spec at http://avalon/connecteddata/Specs/Collection%20Interfaces.mht 12 | // 13 | // History: 14 | // 11/22/2004 : [....] - created 15 | // 16 | //--------------------------------------------------------------------------- 17 | 18 | // Adapted for Unity 19 | 20 | #if !NETFX_CORE 21 | 22 | using System; 23 | using System.Collections; 24 | using System.Collections.Generic; 25 | using System.Collections.Specialized; 26 | using System.ComponentModel; 27 | 28 | namespace System.Collections.ObjectModel 29 | { 30 | /// 31 | /// Implementation of a dynamic data collection based on generic Collection<T>, 32 | /// implementing INotifyCollectionChanged to notify listeners 33 | /// when items get added, removed or the whole list is refreshed. 34 | /// 35 | public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged 36 | { 37 | //------------------------------------------------------ 38 | // 39 | // Constructors 40 | // 41 | //------------------------------------------------------ 42 | 43 | #region Constructors 44 | 45 | /// 46 | /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. 47 | /// 48 | public ObservableCollection() 49 | : base() 50 | { 51 | } 52 | 53 | /// 54 | /// Initializes a new instance of the ObservableCollection class 55 | /// that contains elements copied from the specified list 56 | /// 57 | /// The list whose elements are copied to the new list. 58 | /// 59 | /// The elements are copied onto the ObservableCollection in the 60 | /// same order they are read by the enumerator of the list. 61 | /// 62 | /// list is a null reference 63 | public ObservableCollection(List list) 64 | : base((list != null) ? new List(list.Count) : list) 65 | { 66 | // Workaround for VSWhidbey bug 562681 (tracked by Windows bug 1369339). 67 | // We should be able to simply call the base(list) ctor. But Collection 68 | // doesn't copy the list (contrary to the documentation) - it uses the 69 | // list directly as its storage. So we do the copying here. 70 | // 71 | CopyFrom(list); 72 | } 73 | 74 | /// 75 | /// Initializes a new instance of the ObservableCollection class that contains 76 | /// elements copied from the specified collection and has sufficient capacity 77 | /// to accommodate the number of elements copied. 78 | /// 79 | /// The collection whose elements are copied to the new list. 80 | /// 81 | /// The elements are copied onto the ObservableCollection in the 82 | /// same order they are read by the enumerator of the collection. 83 | /// 84 | /// collection is a null reference 85 | public ObservableCollection(IEnumerable collection) 86 | { 87 | if (collection == null) 88 | throw new ArgumentNullException("collection"); 89 | 90 | CopyFrom(collection); 91 | } 92 | 93 | private void CopyFrom(IEnumerable collection) 94 | { 95 | IList items = Items; 96 | if (collection != null && items != null) 97 | { 98 | using (IEnumerator enumerator = collection.GetEnumerator()) 99 | { 100 | while (enumerator.MoveNext()) 101 | { 102 | items.Add(enumerator.Current); 103 | } 104 | } 105 | } 106 | } 107 | 108 | #endregion Constructors 109 | 110 | //------------------------------------------------------ 111 | // 112 | // Public Methods 113 | // 114 | //------------------------------------------------------ 115 | 116 | #region Public Methods 117 | 118 | /// 119 | /// Move item at oldIndex to newIndex. 120 | /// 121 | public void Move(int oldIndex, int newIndex) 122 | { 123 | MoveItem(oldIndex, newIndex); 124 | } 125 | 126 | #endregion Public Methods 127 | 128 | //------------------------------------------------------ 129 | // 130 | // Public Events 131 | // 132 | //------------------------------------------------------ 133 | 134 | #region Public Events 135 | 136 | //------------------------------------------------------ 137 | /// 138 | /// PropertyChanged event (per ). 139 | /// 140 | public virtual event PropertyChangedEventHandler PropertyChanged; 141 | 142 | //------------------------------------------------------ 143 | /// 144 | /// Occurs when the collection changes, either by adding or removing an item. 145 | /// 146 | /// 147 | /// see 148 | /// 149 | public virtual event NotifyCollectionChangedEventHandler CollectionChanged; 150 | 151 | #endregion Public Events 152 | 153 | //------------------------------------------------------ 154 | // 155 | // Protected Methods 156 | // 157 | //------------------------------------------------------ 158 | 159 | #region Protected Methods 160 | 161 | /// 162 | /// Called by base class Collection<T> when the list is being cleared; 163 | /// raises a CollectionChanged event to any listeners. 164 | /// 165 | protected override void ClearItems() 166 | { 167 | CheckReentrancy(); 168 | base.ClearItems(); 169 | OnPropertyChanged(CountString); 170 | OnPropertyChanged(IndexerName); 171 | OnCollectionReset(); 172 | } 173 | 174 | /// 175 | /// Called by base class Collection<T> when an item is removed from list; 176 | /// raises a CollectionChanged event to any listeners. 177 | /// 178 | protected override void RemoveItem(int index) 179 | { 180 | CheckReentrancy(); 181 | T removedItem = this[index]; 182 | 183 | base.RemoveItem(index); 184 | 185 | OnPropertyChanged(CountString); 186 | OnPropertyChanged(IndexerName); 187 | OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index); 188 | } 189 | 190 | /// 191 | /// Called by base class Collection<T> when an item is added to list; 192 | /// raises a CollectionChanged event to any listeners. 193 | /// 194 | protected override void InsertItem(int index, T item) 195 | { 196 | CheckReentrancy(); 197 | base.InsertItem(index, item); 198 | 199 | OnPropertyChanged(CountString); 200 | OnPropertyChanged(IndexerName); 201 | OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); 202 | } 203 | 204 | /// 205 | /// Called by base class Collection<T> when an item is set in list; 206 | /// raises a CollectionChanged event to any listeners. 207 | /// 208 | protected override void SetItem(int index, T item) 209 | { 210 | CheckReentrancy(); 211 | T originalItem = this[index]; 212 | base.SetItem(index, item); 213 | 214 | OnPropertyChanged(IndexerName); 215 | OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index); 216 | } 217 | 218 | /// 219 | /// Called by base class ObservableCollection<T> when an item is to be moved within the list; 220 | /// raises a CollectionChanged event to any listeners. 221 | /// 222 | protected virtual void MoveItem(int oldIndex, int newIndex) 223 | { 224 | CheckReentrancy(); 225 | 226 | T removedItem = this[oldIndex]; 227 | 228 | base.RemoveItem(oldIndex); 229 | base.InsertItem(newIndex, removedItem); 230 | 231 | OnPropertyChanged(IndexerName); 232 | OnCollectionChanged(NotifyCollectionChangedAction.Move, removedItem, newIndex, oldIndex); 233 | } 234 | 235 | /// 236 | /// Raises a PropertyChanged event (per ). 237 | /// 238 | protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) 239 | { 240 | if (PropertyChanged != null) 241 | { 242 | PropertyChanged(this, e); 243 | } 244 | } 245 | 246 | /// 247 | /// Raise CollectionChanged event to any listeners. 248 | /// Properties/methods modifying this ObservableCollection will raise 249 | /// a collection changed event through this virtual method. 250 | /// 251 | /// 252 | /// When overriding this method, either call its base implementation 253 | /// or call to guard against reentrant collection changes. 254 | /// 255 | protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 256 | { 257 | if (CollectionChanged != null) 258 | { 259 | using (BlockReentrancy()) 260 | { 261 | CollectionChanged(this, e); 262 | } 263 | } 264 | } 265 | 266 | /// 267 | /// Disallow reentrant attempts to change this collection. E.g. a event handler 268 | /// of the CollectionChanged event is not allowed to make changes to this collection. 269 | /// 270 | /// 271 | /// typical usage is to wrap e.g. a OnCollectionChanged call with a using() scope: 272 | /// 273 | /// using (BlockReentrancy()) 274 | /// { 275 | /// CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index)); 276 | /// } 277 | /// 278 | /// 279 | protected IDisposable BlockReentrancy() 280 | { 281 | _monitor.Enter(); 282 | return _monitor; 283 | } 284 | 285 | /// Check and assert for reentrant attempts to change this collection. 286 | /// raised when changing the collection 287 | /// while another collection change is still being notified to other listeners 288 | protected void CheckReentrancy() 289 | { 290 | if (_monitor.Busy) 291 | { 292 | // we can allow changes if there's only one listener - the problem 293 | // only arises if reentrant changes make the original event args 294 | // invalid for later listeners. This keeps existing code working 295 | // (e.g. Selector.SelectedItems). 296 | if ((CollectionChanged != null) && (CollectionChanged.GetInvocationList().Length > 1)) 297 | throw new InvalidOperationException("Cannot modify the collection while reentrancy is blocked."); 298 | } 299 | } 300 | 301 | #endregion Protected Methods 302 | 303 | //------------------------------------------------------ 304 | // 305 | // Private Methods 306 | // 307 | //------------------------------------------------------ 308 | 309 | #region Private Methods 310 | 311 | /// 312 | /// Helper to raise a PropertyChanged event />). 313 | /// 314 | private void OnPropertyChanged(string propertyName) 315 | { 316 | OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 317 | } 318 | 319 | /// 320 | /// Helper to raise CollectionChanged event to any listeners 321 | /// 322 | private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) 323 | { 324 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); 325 | } 326 | 327 | /// 328 | /// Helper to raise CollectionChanged event to any listeners 329 | /// 330 | private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex) 331 | { 332 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex)); 333 | } 334 | 335 | /// 336 | /// Helper to raise CollectionChanged event to any listeners 337 | /// 338 | private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) 339 | { 340 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); 341 | } 342 | 343 | /// 344 | /// Helper to raise CollectionChanged event with action == Reset to any listeners 345 | /// 346 | private void OnCollectionReset() 347 | { 348 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 349 | } 350 | 351 | #endregion Private Methods 352 | 353 | //------------------------------------------------------ 354 | // 355 | // Private Types 356 | // 357 | //------------------------------------------------------ 358 | 359 | #region Private Types 360 | 361 | // this class helps prevent reentrant calls 362 | private class SimpleMonitor : IDisposable 363 | { 364 | public void Enter() 365 | { 366 | ++_busyCount; 367 | } 368 | 369 | public void Dispose() 370 | { 371 | --_busyCount; 372 | } 373 | 374 | public bool Busy { get { return _busyCount > 0; } } 375 | 376 | private int _busyCount; 377 | } 378 | 379 | #endregion Private Types 380 | 381 | //------------------------------------------------------ 382 | // 383 | // Private Fields 384 | // 385 | //------------------------------------------------------ 386 | 387 | #region Private Fields 388 | 389 | private const string CountString = "Count"; 390 | 391 | // This must agree with Binding.IndexerName. It is declared separately 392 | // here so as to avoid a dependency on PresentationFramework.dll. 393 | private const string IndexerName = "Item[]"; 394 | 395 | private SimpleMonitor _monitor = new SimpleMonitor(); 396 | 397 | #endregion Private Fields 398 | } 399 | } 400 | 401 | #endif -------------------------------------------------------------------------------- /ObservableCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 65b19a9acc0ecbd4c9adcfd6167240a0 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UnityMVVM 2 | ========= 3 | --------------------------------------------------------------------------------