diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj
index c4b4e6d..6cb17a6 100644
--- a/Nuclex.Support (net-4.0).csproj
+++ b/Nuclex.Support (net-4.0).csproj
@@ -101,6 +101,10 @@
ObservableDictionary.cs
+
+
+ ObservableList.cs
+
PairPriorityQueue.cs
diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj
index ebcd888..b813099 100644
--- a/Nuclex.Support (xna-4.0-phone7).csproj
+++ b/Nuclex.Support (xna-4.0-phone7).csproj
@@ -132,6 +132,10 @@
ObservableDictionary.cs
+
+
+ ObservableList.cs
+
PairPriorityQueue.cs
diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj
index 39e934d..c7be8eb 100644
--- a/Nuclex.Support (xna-4.0-xbox360).csproj
+++ b/Nuclex.Support (xna-4.0-xbox360).csproj
@@ -143,6 +143,10 @@
ObservableDictionary.cs
+
+
+ ObservableList.cs
+
PairPriorityQueue.cs
diff --git a/Source/Collections/ObservableCollection.Test.cs b/Source/Collections/ObservableCollection.Test.cs
index cc1f98d..41aa18b 100644
--- a/Source/Collections/ObservableCollection.Test.cs
+++ b/Source/Collections/ObservableCollection.Test.cs
@@ -141,12 +141,12 @@ namespace Nuclex.Support.Collections {
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
- /// Tests whether the ItemRemoved event is fired
+ /// Tests whether a the list constructor is working
[Test]
public void TestListConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
- ObservableCollection testCollection = new ObservableCollection(integers);
+ var testCollection = new ObservableCollection(integers);
CollectionAssert.AreEqual(integers, testCollection);
}
diff --git a/Source/Collections/ObservableCollection.cs b/Source/Collections/ObservableCollection.cs
index 0c6a42a..89f5fc1 100644
--- a/Source/Collections/ObservableCollection.cs
+++ b/Source/Collections/ObservableCollection.cs
@@ -86,7 +86,7 @@ namespace Nuclex.Support.Collections {
protected virtual void OnCollectionChanged(
NotifyCollectionChangedAction action, TItem item, int index
) {
- if (CollectionChanged != null) {
+ if(CollectionChanged != null) {
CollectionChanged(
this, new NotifyCollectionChangedEventArgs(action, item, index)
);
@@ -141,30 +141,29 @@ namespace Nuclex.Support.Collections {
/// Fires the 'ItemAdded' event
/// Item that has been added to the collection
protected virtual void OnAdded(TItem item) {
- if (ItemAdded != null)
+ if(ItemAdded != null)
ItemAdded(this, new ItemEventArgs(item));
}
/// Fires the 'ItemRemoved' event
/// Item that has been removed from the collection
protected virtual void OnRemoved(TItem item) {
- if (ItemRemoved != null)
+ if(ItemRemoved != null)
ItemRemoved(this, new ItemEventArgs(item));
}
/// Fires the 'Clearing' event
protected virtual void OnClearing() {
- if (Clearing != null)
+ if(Clearing != null)
Clearing(this, EventArgs.Empty);
}
/// Fires the 'Cleared' event
protected virtual void OnCleared() {
- if (Cleared != null)
+ if(Cleared != null)
Cleared(this, EventArgs.Empty);
}
-
}
} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/ObservableDictionary.cs b/Source/Collections/ObservableDictionary.cs
index c8fcc50..e2890d7 100644
--- a/Source/Collections/ObservableDictionary.cs
+++ b/Source/Collections/ObservableDictionary.cs
@@ -242,6 +242,7 @@ namespace Nuclex.Support.Collections {
protected virtual void OnAdded(KeyValuePair item) {
if(ItemAdded != null)
ItemAdded(this, new ItemEventArgs>(item));
+
#if !NO_SPECIALIZED_COLLECTIONS
if(CollectionChanged != null)
CollectionChanged(
@@ -257,6 +258,7 @@ namespace Nuclex.Support.Collections {
protected virtual void OnRemoved(KeyValuePair item) {
if(ItemRemoved != null)
ItemRemoved(this, new ItemEventArgs>(item));
+
#if !NO_SPECIALIZED_COLLECTIONS
if(CollectionChanged != null)
CollectionChanged(
@@ -277,6 +279,7 @@ namespace Nuclex.Support.Collections {
protected virtual void OnCleared() {
if(Cleared != null)
Cleared(this, EventArgs.Empty);
+
#if !NO_SPECIALIZED_COLLECTIONS
if(CollectionChanged != null)
CollectionChanged(this, CollectionResetEventArgs);
diff --git a/Source/Collections/ObservableList.Test.cs b/Source/Collections/ObservableList.Test.cs
new file mode 100644
index 0000000..181e54f
--- /dev/null
+++ b/Source/Collections/ObservableList.Test.cs
@@ -0,0 +1,165 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2010 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NMock;
+
+namespace Nuclex.Support.Collections {
+
+ /// Unit Test for the observable list class
+ [TestFixture]
+ public class ObservableListTest {
+
+ #region interface IObservableCollectionSubscriber
+
+ /// Interface used to test the observable collection
+ public interface IObservableCollectionSubscriber {
+
+ /// Called when the collection is about to clear its contents
+ /// Collection that is clearing its contents
+ /// Not used
+ void Clearing(object sender, EventArgs arguments);
+
+ /// Called when the collection has been cleared of its contents
+ /// Collection that was cleared of its contents
+ /// Not used
+ void Cleared(object sender, EventArgs arguments);
+
+ /// Called when an item is added to the collection
+ /// Collection to which an item is being added
+ /// Contains the item that is being added
+ void ItemAdded(object sender, ItemEventArgs arguments);
+
+ /// Called when an item is removed from the collection
+ /// Collection from which an item is being removed
+ /// Contains the item that is being removed
+ void ItemRemoved(object sender, ItemEventArgs arguments);
+
+ }
+
+ #endregion // interface IObservableCollectionSubscriber
+
+ /// Initialization routine executed before each test is run
+ [SetUp]
+ public void Setup() {
+ this.mockery = new MockFactory();
+
+ this.mockedSubscriber = this.mockery.CreateMock();
+
+ this.observedList = new ObservableList();
+ this.observedList.Clearing += new EventHandler(
+ this.mockedSubscriber.MockObject.Clearing
+ );
+ this.observedList.Cleared += new EventHandler(
+ this.mockedSubscriber.MockObject.Cleared
+ );
+ this.observedList.ItemAdded += new EventHandler>(
+ this.mockedSubscriber.MockObject.ItemAdded
+ );
+ this.observedList.ItemRemoved += new EventHandler>(
+ this.mockedSubscriber.MockObject.ItemRemoved
+ );
+ }
+
+ /// Tests whether the Clearing event is fired
+ [Test]
+ public void TestClearingEvent() {
+ this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
+ this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
+ this.observedList.Clear();
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ /// Tests whether the ItemAdded event is fired
+ [Test]
+ public void TestItemAddedEvent() {
+ this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
+
+ this.observedList.Add(123);
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ /// Tests whether the ItemRemoved event is fired
+ [Test]
+ public void TestItemRemovedEvent() {
+ this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
+
+ this.observedList.Add(123);
+
+ this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
+
+ this.observedList.Remove(123);
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ /// Tests whether items in the collection can be replaced
+ [Test]
+ public void TestItemReplacement() {
+ this.mockedSubscriber.Expects.Exactly(3).Method(
+ m => m.ItemAdded(null, null)
+ ).WithAnyArguments();
+
+ this.observedList.Add(1);
+ this.observedList.Add(2);
+ this.observedList.Add(3);
+
+ this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
+ this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
+
+ // Replace the middle item with something else
+ this.observedList[1] = 4;
+
+ Assert.AreEqual(
+ 1, this.observedList.IndexOf(4)
+ );
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ /// Tests whether a the list constructor is working
+ [Test]
+ public void TestListConstructor() {
+ int[] integers = new int[] { 12, 34, 56, 78 };
+
+ var testList = new ObservableList(integers);
+
+ CollectionAssert.AreEqual(integers, testList);
+ }
+
+ /// Mock object factory
+ private MockFactory mockery;
+ /// The mocked observable collection subscriber
+ private Mock mockedSubscriber;
+ /// An observable collection to which a mock will be subscribed
+ private ObservableList observedList;
+
+ }
+
+} // namespace Nuclex.Support.Collections
+
+#endif // UNITTEST
diff --git a/Source/Collections/ObservableList.cs b/Source/Collections/ObservableList.cs
new file mode 100644
index 0000000..f6ce57c
--- /dev/null
+++ b/Source/Collections/ObservableList.cs
@@ -0,0 +1,374 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2010 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+#if !NO_SPECIALIZED_COLLECTIONS
+using System.Collections.Specialized;
+#endif
+
+namespace Nuclex.Support.Collections {
+
+ /// List which fires events when items are added or removed
+ /// Type of items the collection manages
+ public class ObservableList : IList, IList, ICollection,
+#if !NO_SPECIALIZED_COLLECTIONS
+ INotifyCollectionChanged,
+#endif
+ IObservableCollection {
+
+ /// Raised when an item has been added to the collection
+ public event EventHandler> ItemAdded;
+ /// Raised when an item is removed from the collection
+ public event EventHandler> ItemRemoved;
+ /// Raised when the collection is about to be cleared
+ ///
+ /// This could be covered by calling ItemRemoved for each item currently
+ /// contained in the collection, but it is often simpler and more efficient
+ /// to process the clearing of the entire collection as a special operation.
+ ///
+ public event EventHandler Clearing;
+ /// Raised when the collection has been cleared
+ public event EventHandler Cleared;
+
+#if !NO_SPECIALIZED_COLLECTIONS
+ /// Called when the collection has changed
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+#endif
+
+ ///
+ /// Initializes a new instance of the ObservableList class that is empty.
+ ///
+ public ObservableList() : this(new List()) { }
+
+ ///
+ /// Initializes a new instance of the ObservableList class as a wrapper
+ /// for the specified list.
+ ///
+ /// The list that is wrapped by the new collection.
+ /// List is null
+ public ObservableList(IList list) {
+ this.typedList = list;
+ this.objectList = list as IList; // Gah!
+ }
+
+ /// Determines the index of the specified item in the list
+ /// Item whose index will be determined
+ /// The index of the item in the list or -1 if not found
+ public int IndexOf(TItem item) {
+ return this.typedList.IndexOf(item);
+ }
+
+ /// Inserts an item into the list at the specified index
+ /// Index the item will be insertted at
+ /// Item that will be inserted into the list
+ public void Insert(int index, TItem item) {
+ this.typedList.Insert(index, item);
+ OnAdded(item);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
+#endif
+ }
+
+ /// Removes the item at the specified index from the list
+ /// Index at which the item will be removed
+ public void RemoveAt(int index) {
+ TItem item = this.typedList[index];
+ this.typedList.RemoveAt(index);
+ OnRemoved(item);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
+#endif
+ }
+
+ /// Accesses the item at the specified index in the list
+ /// Index of the item that will be accessed
+ /// The item at the specified index
+ public TItem this[int index] {
+ get { return this.typedList[index]; }
+ set {
+ TItem oldItem = this.typedList[index];
+ this.typedList[index] = value;
+ OnRemoved(oldItem);
+ OnAdded(value);
+#if !NO_SPECIALIZED_COLLECTIONS
+ if(CollectionChanged != null) {
+ CollectionChanged(
+ this, new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Replace, value, oldItem, index
+ )
+ );
+ }
+#endif
+
+ }
+ }
+
+ /// Adds an item to the end of the list
+ /// Item that will be added to the list
+ public void Add(TItem item) {
+ this.typedList.Add(item);
+ OnAdded(item);
+ }
+
+ /// Removes all items from the list
+ public void Clear() {
+ OnClearing();
+ this.typedList.Clear();
+ OnCleared();
+#if !NO_SPECIALIZED_COLLECTIONS
+ if(CollectionChanged != null) {
+ CollectionChanged(this, CollectionResetEventArgs);
+ }
+#endif
+ }
+
+ /// Checks whether the list contains the specified item
+ /// Item the list will be checked for
+ /// True if the list contains the specified items
+ public bool Contains(TItem item) {
+ return this.typedList.Contains(item);
+ }
+
+ /// Copies the contents of the list into an array
+ /// Array the list will be copied into
+ ///
+ /// Index in the target array where the first item will be copied to
+ ///
+ public void CopyTo(TItem[] array, int arrayIndex) {
+ this.typedList.CopyTo(array, arrayIndex);
+ }
+
+ /// Total number of items in the list
+ public int Count {
+ get { return this.typedList.Count; }
+ }
+
+ /// Whether the list is a read-only list
+ public bool IsReadOnly {
+ get { return this.typedList.IsReadOnly; }
+ }
+
+ /// Removes the specified item from the list
+ /// Item that will be removed from the list
+ ///
+ /// True if the item was found and removed from the list, false otherwise
+ ///
+ public bool Remove(TItem item) {
+ int index = this.typedList.IndexOf(item);
+ if(index == -1) {
+ return false;
+ }
+
+ TItem removedItem = this.typedList[index];
+ this.typedList.RemoveAt(index);
+ OnRemoved(removedItem);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
+#endif
+ return true;
+ }
+
+ /// Returns an enumerator for the items in the list
+ /// An enumerator for the list's items
+ public IEnumerator GetEnumerator() {
+ return this.typedList.GetEnumerator();
+ }
+
+ #region IEnumerable implementation
+
+ /// Returns an enumerator for the items in the list
+ /// An enumerator for the list's items
+ IEnumerator IEnumerable.GetEnumerator() {
+ return this.objectList.GetEnumerator();
+ }
+
+ #endregion // IEnumerable implementation
+
+ #region ICollection implementation
+
+ /// Copies the contents of the list into an array
+ /// Array the list will be copied into
+ ///
+ /// Index in the target array where the first item will be copied to
+ ///
+ void ICollection.CopyTo(Array array, int arrayIndex) {
+ this.objectList.CopyTo(array, arrayIndex);
+ }
+
+ /// Whether this list performs thread synchronization
+ bool ICollection.IsSynchronized {
+ get { return this.objectList.IsSynchronized; }
+ }
+
+ /// Synchronization root used by the list to synchronize threads
+ object ICollection.SyncRoot {
+ get { return this.objectList.SyncRoot; }
+ }
+
+ #endregion // ICollection implementation
+
+ #region IList implementation
+
+ /// Adds an item to the list
+ /// Item that will be added to the list
+ ///
+ /// The position at which the item has been inserted or -1 if the item was not inserted
+ ///
+ int IList.Add(object value) {
+ int index = this.objectList.Add(value);
+ TItem addedItem = this.typedList[index];
+ OnAdded(addedItem);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, addedItem, index);
+#endif
+ return index;
+ }
+
+ /// Checks whether the list contains the specified item
+ /// Item the list will be checked for
+ /// True if the list contains the specified items
+ bool IList.Contains(object item) {
+ return this.objectList.Contains(item);
+ }
+
+ /// Determines the index of the specified item in the list
+ /// Item whose index will be determined
+ /// The index of the item in the list or -1 if not found
+ int IList.IndexOf(object item) {
+ return this.objectList.IndexOf(item);
+ }
+
+ /// Inserts an item into the list at the specified index
+ /// Index the item will be insertted at
+ /// Item that will be inserted into the list
+ void IList.Insert(int index, object item) {
+ this.objectList.Insert(index, item);
+ TItem addedItem = this.typedList[index];
+ OnAdded(addedItem);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, addedItem, index);
+#endif
+ }
+
+ /// Whether the list is of a fixed size
+ bool IList.IsFixedSize {
+ get { return this.objectList.IsFixedSize; }
+ }
+
+ /// Removes the specified item from the list
+ /// Item that will be removed from the list
+ void IList.Remove(object item) {
+ int index = this.objectList.IndexOf(item);
+ if(index == -1) {
+ return;
+ }
+
+ TItem removedItem = this.typedList[index];
+ this.objectList.RemoveAt(index);
+ OnRemoved(removedItem);
+#if !NO_SPECIALIZED_COLLECTIONS
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
+#endif
+ }
+
+ /// Accesses the item at the specified index in the list
+ /// Index of the item that will be accessed
+ /// The item at the specified index
+ object IList.this[int index] {
+ get { return this.objectList[index]; }
+ set {
+ TItem oldItem = this.typedList[index];
+ this.objectList[index] = value;
+ TItem newItem = this.typedList[index];
+ OnRemoved(oldItem);
+ OnAdded(newItem);
+#if !NO_SPECIALIZED_COLLECTIONS
+ if(CollectionChanged != null) {
+ CollectionChanged(
+ this, new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Replace, newItem, oldItem, index
+ )
+ );
+ }
+#endif
+ }
+ }
+
+ #endregion // IList implementation
+
+#if !NO_SPECIALIZED_COLLECTIONS
+ /// Fires the CollectionChanged event
+ /// Type of change that has occured
+ /// The item that has been added, removed or replaced
+ /// Index of the changed item
+ protected virtual void OnCollectionChanged(
+ NotifyCollectionChangedAction action, TItem item, int index
+ ) {
+ if(CollectionChanged != null) {
+ CollectionChanged(
+ this, new NotifyCollectionChangedEventArgs(action, item, index)
+ );
+ }
+ }
+#endif
+
+ /// Fires the 'ItemAdded' event
+ /// Item that has been added to the collection
+ protected virtual void OnAdded(TItem item) {
+ if(ItemAdded != null)
+ ItemAdded(this, new ItemEventArgs(item));
+ }
+
+ /// Fires the 'ItemRemoved' event
+ /// Item that has been removed from the collection
+ protected virtual void OnRemoved(TItem item) {
+ if(ItemRemoved != null)
+ ItemRemoved(this, new ItemEventArgs(item));
+ }
+
+ /// Fires the 'Clearing' event
+ protected virtual void OnClearing() {
+ if(Clearing != null)
+ Clearing(this, EventArgs.Empty);
+ }
+
+ /// Fires the 'Cleared' event
+ protected virtual void OnCleared() {
+ if(Cleared != null)
+ Cleared(this, EventArgs.Empty);
+ }
+
+#if !NO_SPECIALIZED_COLLECTIONS
+ /// Fixed event args used to notify that the collection has reset
+ private static readonly NotifyCollectionChangedEventArgs CollectionResetEventArgs =
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+#endif
+
+ /// The wrapped List under its type-safe interface
+ private IList typedList;
+ /// The wrapped List under its object interface
+ private IList objectList;
+
+ }
+
+} // namespace Nuclex.Support.Collections