1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing.Model
7 using System.Collections.Generic;
8 using System.Collections.Specialized;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
14 using System.Activities.Presentation;
15 using System.Activities.Presentation.Model;
16 using System.Activities.Presentation.PropertyEditing;
18 using System.Activities.Presentation.Internal.Properties;
22 // Collection of ModelPropertyValues used to model collections of ModelItems
24 internal class ModelPropertyValueCollection : PropertyValueCollection
27 private List<ModelPropertyIndexer> _values;
28 private bool _listenToCollectionChanges = true;
33 // <param name="parentValue">Parent PropertyValue</param>
34 public ModelPropertyValueCollection(ModelPropertyValue parentValue) : base(parentValue)
36 if (parentValue == null)
38 throw FxTrace.Exception.ArgumentNull("parentValue");
41 // Wrap each existing item in the collection in ModelPropertyEntryIndexer
42 ModelItemCollection collection = this.GetRawCollection();
43 if (collection != null && collection.Count > 0)
45 _values = new List<ModelPropertyIndexer>();
47 foreach (ModelItem item in collection)
49 _values.Add(CreateModelPropertyIndexer(item, i++));
53 // Hook into the collection changed events
54 if (collection != null)
56 collection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnUnderlyingCollectionChanged);
61 // Gets the number of items in this collection
63 public override int Count
66 return _values == null ? 0 : _values.Count;
71 // Gets the PropertyValue at the specified index
73 // <param name="index">Index to look up</param>
74 // <returns>PropertyValue at the specified index</returns>
75 public override PropertyValue this[int index] {
77 VerifyExistingIndex(index);
78 return _values[index].PropertyValue;
83 // Adds the specified object to this collection, returning its wrapped version
85 // <param name="value">Value to add and wrap in PropertyValue</param>
86 // <returns>Wrapped value</returns>
87 public override PropertyValue Add(object value)
89 return Insert(value, this.Count);
93 // Inserts the specified object into this collection, returning its wrapped version
95 // <param name="value">Value to insert and wrap</param>
96 // <param name="index">Index to insert at</param>
97 // <returns>Wrapped version of the inserted value</returns>
98 public override PropertyValue Insert(object value, int index)
100 VerifyNewIndex(index);
104 _values = new List<ModelPropertyIndexer>();
108 bool previouslyActive = SnoozeListeningToCollectionChanges();
111 item = GetRawCollection().Insert(index, value);
115 StartListeningToCollectionChanges(previouslyActive);
118 return InsertExternal(item, index);
121 // Same as Insert(), except it doesn't modify the raw collection, because it assumes
122 // that the raw collection was already modified externally.
123 private PropertyValue InsertExternal(ModelItem item, int index)
127 _values = new List<ModelPropertyIndexer>();
130 PropertyValue insertedValue = InsertHelper(item, index);
132 // Fire OnChanged event
133 this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, insertedValue, index));
135 return insertedValue;
138 // Updates internal structures, but does not fire any notification
139 private PropertyValue InsertHelper(ModelItem item, int index)
141 // Only insert the value into the collection, if it's not already there.
142 // Say that an ItemAdded event comes in even though this collection has not yet been used.
143 // By requesting the instance of this collection for the first time, the collection
144 // gets populated correctly and fully. Now when InsertExternal is called as a result
145 // of the ItemAdded event, we would be adding the new item into the collection twice.
146 // We need to prevent that from happening.
147 if (_values.Count > index &&
148 object.Equals(_values[index].ModelItem, item))
150 return _values[index].PropertyValue;
153 ModelPropertyIndexer indexer = CreateModelPropertyIndexer(item, index);
154 _values.Insert(index, indexer);
156 // Adjust all indexes of the remaining indexers in the list
157 for (int i = index + 1; i < _values.Count; i++)
162 return indexer.PropertyValue;
166 // Removes the specified PropertyValue from the collection.
168 // <param name="property">Property to remove</param>
169 // <returns>True, if the PropertyValue was found and removed, false
170 // otherwise.</returns>
171 public override bool Remove(PropertyValue propertyValue)
173 if (propertyValue == null)
175 throw FxTrace.Exception.ArgumentNull("property");
183 for (int i = 0; i < _values.Count; i++)
185 if (_values[i].PropertyValue == propertyValue) {
191 // Call to RemoveAt() already fires the right CollectionChanged events
195 // Same as Remove, except it doesn't modify the raw collection, because it's
196 // assumed that the raw collection was already modified externally.
197 private bool RemoveExternal(ModelItem item)
199 Fx.Assert(item != null, "item parameter should not be null");
200 Fx.Assert(_values != null, "_values parameter should not be null");
202 for (int i = 0; i < _values.Count; i++)
204 if (_values[i].ModelItem == item) {
205 this.RemoveAtExternal(i);
214 // Removes the PropertyValue at the specified index.
216 // <param name="index">Index at which to remove the value.</param>
217 public override void RemoveAt(int index)
219 VerifyExistingIndex(index);
221 bool previouslyActive = SnoozeListeningToCollectionChanges();
224 this.GetRawCollection().RemoveAt(index);
228 StartListeningToCollectionChanges(previouslyActive);
231 RemoveAtExternal(index);
234 // Same as RemoveAt, except it doesn't modify the raw collection, because it's
235 // assumed that the raw collection was already modified externally.
236 private void RemoveAtExternal(int index)
238 VerifyExistingIndex(index);
239 PropertyValue removedValue = RemoveAtHelper(index);
241 // Fire OnChanged event
242 this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedValue, index));
245 // Updates internal structures, but does not fire any notification
246 private PropertyValue RemoveAtHelper(int index)
248 // invalidate the ModelPropertyEntryIndexer at the index and adjust all other indexes
249 ModelPropertyIndexer indexer = _values[index];
250 DestroyModelPropertyIndexer(indexer);
252 _values.RemoveAt(index);
253 for (int i = index; i < _values.Count; i++)
258 return indexer.PropertyValue;
261 // Replaces the old ModelItem with the new one, assuming that the raw collection
262 // has already been verified
263 private void ReplaceExternal(ModelItem oldItem, ModelItem newItem)
265 Fx.Assert(_values != null, "_values parameter should not be null");
266 Fx.Assert(oldItem != null, "oldItem parameter should not be null");
267 Fx.Assert(newItem != null, "newItem parameter should not be null");
269 for (int i = 0; i < _values.Count; i++)
271 if (_values[i].ModelItem == oldItem) {
272 this.RemoveAtHelper(i);
273 this.InsertHelper(newItem, i);
275 // Fire OnChanged event
276 this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _values[i].PropertyValue));
281 Debug.Fail("Didn't find the expected item to remove");
284 // Clears the collection, assuming that the raw collection was already cleared externally
285 private void ClearExternal()
288 this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
292 // Swaps the items at the specified indexes
294 // <param name="currentIndex">Index of item 1</param>
295 // <param name="newIndex">Index of item 2</param>
296 public override void SetIndex(int currentIndex, int newIndex)
299 VerifyExistingIndex(currentIndex);
300 VerifyExistingIndex(newIndex);
302 if (currentIndex == newIndex)
307 ModelItemCollection collection = this.GetRawCollection();
308 ModelPropertyIndexer currentIndexer = _values[currentIndex];
309 ModelPropertyIndexer newIndexer = _values[newIndex];
311 bool previouslyActive = SnoozeListeningToCollectionChanges();
314 // Remove the higher index first (doesn't affect the value of the lower index)
315 if (currentIndex < newIndex)
317 collection.RemoveAt(newIndex);
318 collection.RemoveAt(currentIndex);
322 collection.RemoveAt(currentIndex);
323 collection.RemoveAt(newIndex);
326 // Insert the lower index first (fixes the value of the higher index)
327 if (currentIndex < newIndex)
329 collection.Insert(currentIndex, newIndexer.ModelItem);
330 collection.Insert(newIndex, currentIndexer.ModelItem);
334 collection.Insert(newIndex, currentIndexer.ModelItem);
335 collection.Insert(currentIndex, newIndexer.ModelItem);
340 StartListeningToCollectionChanges(previouslyActive);
343 SetIndexExternal(currentIndex, newIndex);
346 // Same as SetIndex, except it doesn't modify the raw collection, because it's
347 // assumed that the raw collection was already modified externally.
348 private void SetIndexExternal(int currentIndex, int newIndex)
351 if (currentIndex == newIndex)
356 ModelPropertyIndexer currentIndexer = _values[currentIndex];
357 ModelPropertyIndexer newIndexer = _values[newIndex];
359 // Remove the higher index first (doesn't affect the value of the lower index)
360 if (currentIndex < newIndex)
362 _values.RemoveAt(newIndex);
363 _values.RemoveAt(currentIndex);
367 _values.RemoveAt(currentIndex);
368 _values.RemoveAt(newIndex);
371 // Insert the lower index first (fixes the value of the higher index)
372 if (currentIndex < newIndex)
374 _values.Insert(currentIndex, newIndexer);
375 _values.Insert(newIndex, currentIndexer);
379 _values.Insert(newIndex, currentIndexer);
380 _values.Insert(currentIndex, newIndexer);
383 newIndexer.Index = currentIndex;
384 currentIndexer.Index = newIndex;
386 // Fire OnChanged event
387 this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
388 NotifyCollectionChangedAction.Move, currentIndexer.PropertyValue, newIndex, currentIndex));
392 // Gets the underlying ModelItemCollection
394 // <returns>The underlying ModelItemCollection</returns>
395 internal ModelItemCollection GetRawCollection()
397 ModelPropertyEntry parentAsEntry = ParentValue.ParentProperty as ModelPropertyEntry;
398 if (parentAsEntry != null)
400 return parentAsEntry.FirstModelProperty.Collection;
403 ModelPropertyIndexer parentAsIndexer = ParentValue.ParentProperty as ModelPropertyIndexer;
404 if (parentAsIndexer != null)
406 ModelItemCollection modelItemCollection = parentAsIndexer.ModelItem as ModelItemCollection;
408 // If the parent is an indexer, that means we are a collection within another collection
409 // and the ModelItem of the indexer is really a ModelItemCollection.
410 Fx.Assert(modelItemCollection != null, "modelItemCollection should not be null");
412 return modelItemCollection;
415 Debug.Fail("A new class was introduced that derives from PropertyEntry. Need to update ModelPropertyValueCollection code as well.");
420 // Gets the enumerator over this collection
422 // <returns>Enumerator over this collection</returns>
423 public override IEnumerator<PropertyValue> GetEnumerator()
430 foreach (ModelPropertyIndexer value in _values)
432 yield return value.PropertyValue;
436 // Handler for all collection changed events that happen through the model
437 private void OnUnderlyingCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
439 if (!_listenToCollectionChanges)
446 case NotifyCollectionChangedAction.Add:
447 int startingIndex = e.NewStartingIndex < 0 ? this.Count : e.NewStartingIndex;
448 foreach (ModelItem item in e.NewItems)
450 this.InsertExternal(item, startingIndex++);
454 case NotifyCollectionChangedAction.Remove:
455 foreach (ModelItem item in e.OldItems)
457 this.RemoveExternal(item);
461 case NotifyCollectionChangedAction.Move:
462 int oldIndex = e.OldStartingIndex, newIndex = e.NewStartingIndex;
463 for (int i = 0; i < e.OldItems.Count; i++)
465 this.SetIndexExternal(oldIndex++, newIndex++);
470 case NotifyCollectionChangedAction.Replace:
471 for (int i = 0; i < e.OldItems.Count; i++)
473 ModelItem oldItem = e.OldItems[i] as ModelItem;
474 ModelItem newItem = e.NewItems[i] as ModelItem;
475 this.ReplaceExternal(oldItem, newItem);
479 case NotifyCollectionChangedAction.Reset:
480 this.ClearExternal();
485 // Activates the CollectionChanged event handler
486 private void StartListeningToCollectionChanges(bool previousValue)
488 _listenToCollectionChanges = previousValue;
491 // Suspends the CollectionChanged event handler
492 private bool SnoozeListeningToCollectionChanges()
494 bool previousValue = _listenToCollectionChanges;
495 _listenToCollectionChanges = false;
496 return previousValue;
499 [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
500 private void VerifyExistingIndex(int index)
502 if (_values == null || _values.Count <= index || index < 0)
504 throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
508 [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
509 private void VerifyNewIndex(int index)
511 if ((_values == null && index != 0) ||
512 (_values != null && _values.Count < index) ||
515 throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
519 private ModelPropertyIndexer CreateModelPropertyIndexer(ModelItem item, int index)
521 ModelPropertyIndexer indexer = new ModelPropertyIndexer(item, index, this);
525 private static void DestroyModelPropertyIndexer(ModelPropertyIndexer indexer)