diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj
index 65713bc..85e2d5b 100644
--- a/Nuclex.Support (net-4.0).csproj
+++ b/Nuclex.Support (net-4.0).csproj
@@ -194,6 +194,10 @@
ReverseComparer.cs
+
+
+ SortableBindingList.cs
+
TransformingReadOnlyCollection.cs
diff --git a/Source/Collections/IListExtensions.Test.cs b/Source/Collections/IListExtensions.Test.cs
index a6c70e6..7bd8240 100644
--- a/Source/Collections/IListExtensions.Test.cs
+++ b/Source/Collections/IListExtensions.Test.cs
@@ -32,6 +32,20 @@ namespace Nuclex.Support.Collections {
[TestFixture]
internal class IListExtensionsTest {
+ /// Tests whether the insertion sort algorithm sorts a list correctly
+ [Test]
+ public void InsertionSortCanSortWholeList() {
+ var testList = new List(capacity: 5) { 1, 5, 2, 4, 3 };
+ var testListAsIList = (IList)testList;
+
+ testListAsIList.InsertionSort();
+
+ CollectionAssert.AreEqual(
+ new List(capacity: 5) { 1, 2, 3, 4, 5 },
+ testList
+ );
+ }
+
/// Tests whether the insertion sort algorithm works on big lists
[Test]
public void InsertionSortCanSortBigList() {
@@ -53,26 +67,12 @@ namespace Nuclex.Support.Collections {
}
}
- /// Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected
- [Test]
- public void InsertionSortCanSortWholeList() {
- var testList = new List(capacity: 5) { 1, 5, 2, 4, 3 };
- var testListAsIList = (IList)testList;
-
- testListAsIList.InsertionSort();
-
- CollectionAssert.AreEqual(
- new List(capacity: 5) { 1, 2, 3, 4, 5 },
- testList
- );
- }
-
- /// Tests whether the 'Text' property works as expected
+ /// Tests whether the insertion sort algorithm respects custom boundaries
[Test]
public void InsertionSortCanSortListSegment() {
var testList = new List(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testListAsIList = (IList)testList;
-
+
testListAsIList.InsertionSort(1, 5, Comparer.Default);
CollectionAssert.AreEqual(
@@ -81,6 +81,20 @@ namespace Nuclex.Support.Collections {
);
}
+ /// Tests whether the quicksort algorithm sorts a list correctly
+ [Test]
+ public void QuickSortCanSortWholeList() {
+ var testList = new List(capacity: 5) { 1, 5, 2, 4, 3 };
+ var testListAsIList = (IList)testList;
+
+ testListAsIList.QuickSort();
+
+ CollectionAssert.AreEqual(
+ new List(capacity: 5) { 1, 2, 3, 4, 5 },
+ testList
+ );
+ }
+
/// Tests whether the quicksort algorithm works on big lists
[Test]
public void QuickSortCanSortBigList() {
@@ -102,21 +116,20 @@ namespace Nuclex.Support.Collections {
}
}
- /// Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected
+ /// Tests whether the quicksort algorithm respects custom boundaries
[Test]
- public void QuickSortCanSortWholeList() {
- var testList = new List(capacity: 5) { 1, 5, 2, 4, 3 };
+ public void QuickSortCanSortListSegment() {
+ var testList = new List(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testListAsIList = (IList)testList;
-
- testListAsIList.QuickSort();
+
+ testListAsIList.QuickSort(1, 5, Comparer.Default);
CollectionAssert.AreEqual(
- new List(capacity: 5) { 1, 2, 3, 4, 5 },
+ new List(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
testList
);
}
-
}
} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/IListExtensions.cs b/Source/Collections/IListExtensions.cs
index 8f79aa3..5cbe824 100644
--- a/Source/Collections/IListExtensions.cs
+++ b/Source/Collections/IListExtensions.cs
@@ -19,7 +19,6 @@ License along with this library
#endregion
using System;
-using System.Collections;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
diff --git a/Source/Collections/SortableBindingList.Test.cs b/Source/Collections/SortableBindingList.Test.cs
new file mode 100644
index 0000000..0ebab8c
--- /dev/null
+++ b/Source/Collections/SortableBindingList.Test.cs
@@ -0,0 +1,120 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2017 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;
+using System.ComponentModel;
+using System.Reflection;
+
+#if UNITTEST
+
+using NUnit.Framework;
+
+namespace Nuclex.Support.Collections {
+
+ /// Unit Test for the SortableBindingList class
+ [TestFixture]
+ internal class SortableBindingListTest {
+
+ #region class TestRecord
+
+ /// Dummy record used to test the sortable binding list
+ private class TestRecord {
+
+ /// A property of type integer
+ public int IntegerValue { get; set; }
+
+ /// A property of type string
+ public string StringValue { get; set; }
+
+ /// A property of type float
+ public float FloatValue { get; set; }
+
+ }
+
+ #endregion // class TestRecord
+
+ /// Verifies that the sortable binding list is default constructible
+ [Test]
+ public void HasDefaultConstructor() {
+ Assert.DoesNotThrow(
+ delegate () { new SortableBindingList(); }
+ );
+ }
+
+ ///
+ /// Tests whether the sortable binding list can copy an existing list
+ /// when being constructed
+ ///
+ [Test]
+ public void HasEnumerableConstructor() {
+ var items = new List() {
+ new TestRecord() { IntegerValue = 123 },
+ new TestRecord() { IntegerValue = 456 }
+ };
+
+ var testList = new SortableBindingList(items);
+
+ Assert.AreEqual(2, testList.Count);
+ Assert.AreSame(items[0], testList[0]);
+ Assert.AreSame(items[1], testList[1]);
+ }
+
+ /// Verifies that the sortable binding list supports sorting
+ [Test]
+ public void SupportsSorting() {
+ var testList = new SortableBindingList();
+ IBindingList testListAsBindingList = testList;
+
+ Assert.IsTrue(testListAsBindingList.SupportsSorting);
+ }
+
+ ///
+ /// Tests whether the sortable binding list can sort its elements by different properties
+ ///
+ [Test]
+ public void CanSortItems() {
+ var items = new List() {
+ new TestRecord() { IntegerValue = 456 },
+ new TestRecord() { IntegerValue = 789 },
+ new TestRecord() { IntegerValue = 123 }
+ };
+
+ var testList = new SortableBindingList(items);
+ IBindingList testListAsBindingList = testList;
+
+ PropertyDescriptor integerValuePropertyDescriptor = (
+ TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
+ );
+ testListAsBindingList.ApplySort(
+ integerValuePropertyDescriptor, ListSortDirection.Ascending
+ );
+
+ Assert.AreEqual(3, testList.Count);
+ Assert.AreEqual(123, testList[0].IntegerValue);
+ Assert.AreEqual(456, testList[1].IntegerValue);
+ Assert.AreEqual(789, testList[2].IntegerValue);
+ }
+
+ }
+
+} // namespace Nuclex.Support.Collections
+
+#endif // UNITTEST
diff --git a/Source/Collections/SortableBindingList.cs b/Source/Collections/SortableBindingList.cs
new file mode 100644
index 0000000..1ad125e
--- /dev/null
+++ b/Source/Collections/SortableBindingList.cs
@@ -0,0 +1,223 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2017 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;
+using System.ComponentModel;
+using System.Reflection;
+
+namespace Nuclex.Support.Collections {
+
+ /// Variant of BindingList that supports sorting
+ /// Type of items the binding list will contain
+ internal class SortableBindingList : BindingList {
+
+ #region class PropertyComparer
+
+ /// Compares two elements based on a single preselected property
+ private class PropertyComparer : IComparer {
+
+ /// Initializes a new property comparer for the specified property
+ /// Property based on which elements should be compared
+ /// Direction in which elements should be sorted
+ public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
+ this.propertyDescriptor = property;
+
+ Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
+ this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
+ "Default",
+ BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
+ null, // binder
+ null, // target (none since method is static)
+ null // argument array
+ );
+
+ SetListSortDirection(direction);
+ }
+
+ /// Compares two elements based on the comparer's chosen property
+ /// First element for the comparison
+ /// Second element for the comparison
+ /// The relationship of the two elements to each other
+ public int Compare(TElement first, TElement second) {
+ return this.comparer.Compare(
+ this.propertyDescriptor.GetValue(first),
+ this.propertyDescriptor.GetValue(second)
+ ) * this.reverse;
+ }
+
+ /// Selects the property based on which elements should be compared
+ /// Descriptor for the property to use for comparison
+ private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
+ this.propertyDescriptor = descriptor;
+ }
+
+ /// Changes the sort direction
+ /// New sort direction
+ private void SetListSortDirection(ListSortDirection direction) {
+ this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
+ }
+
+ /// Updtes the sorted proeprty and the sort direction
+ /// Property based on which elements will be sorted
+ /// Direction in which elements will be sorted
+ public void SetPropertyAndDirection(
+ PropertyDescriptor descriptor, ListSortDirection direction
+ ) {
+ SetPropertyDescriptor(descriptor);
+ SetListSortDirection(direction);
+ }
+
+ /// The default comparer for the type of the chosen property
+ private readonly IComparer comparer;
+ /// Descriptor for the chosen property
+ private PropertyDescriptor propertyDescriptor;
+ ///
+ /// Either positive or negative 1 to change the sign of the comparison result
+ ///
+ private int reverse;
+
+ }
+
+ #endregion // class PropertyComparer
+
+ /// Initializes a new BindingList with support for sorting
+ public SortableBindingList() : base(new List()) {
+ this.comparers = new Dictionary();
+ }
+
+ ///
+ /// Initializes a sortable BindingList, copying the contents of an existing list
+ ///
+ /// Existing list whose contents will be shallo-wcopied
+ public SortableBindingList(IEnumerable enumeration) :
+ base(new List(enumeration)) {
+ this.comparers = new Dictionary();
+ }
+
+ ///
+ /// Used by BindingList implementation to check whether sorting is supported
+ ///
+ protected override bool SupportsSortingCore {
+ get { return true; }
+ }
+
+ ///
+ /// Used by BindingList implementation to check whether the list is currently sorted
+ ///
+ protected override bool IsSortedCore {
+ get { return this.isSorted; }
+ }
+
+ ///
+ /// Used by BindingList implementation to track the property the list is sorted by
+ ///
+ protected override PropertyDescriptor SortPropertyCore {
+ get { return this.propertyDescriptor; }
+ }
+
+ ///
+ /// Used by BindingList implementation to track the direction in which the list is sortd
+ ///
+ protected override ListSortDirection SortDirectionCore {
+ get { return this.listSortDirection; }
+ }
+
+ ///
+ /// Used by BindingList implementation to check whether the list supports searching
+ ///
+ protected override bool SupportsSearchingCore {
+ get { return true; }
+ }
+
+ ///
+ /// Used by BindingList implementation to sort the elements in the backing collection
+ ///
+ protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
+
+ // Obtain a property comparer that sorts on the attributes the SortableBindingList
+ // has been configured for its sort order
+ PropertyComparer comparer;
+ {
+ Type propertyType = property.PropertyType;
+
+ if(!this.comparers.TryGetValue(propertyType, out comparer)) {
+ comparer = new PropertyComparer(property, direction);
+ this.comparers.Add(propertyType, comparer);
+ }
+
+ // Direction may need to be updated
+ comparer.SetPropertyAndDirection(property, direction);
+ }
+
+ // Check to see if our base class is using a standard List<> in which case
+ // we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
+ // there's still our own quicksort implementation for IList<>.
+ List itemsAsList = this.Items as List;
+ if(itemsAsList != null) {
+ itemsAsList.Sort(comparer);
+ } else {
+ this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
+ }
+
+ this.propertyDescriptor = property;
+ this.listSortDirection = direction;
+ this.isSorted = true;
+
+ OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
+ }
+
+ /// Used by BindingList implementation to undo any sorting that took place
+ protected override void RemoveSortCore() {
+ this.isSorted = false;
+ this.propertyDescriptor = base.SortPropertyCore;
+ this.listSortDirection = base.SortDirectionCore;
+
+ OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
+ }
+
+ ///
+ /// Used by BindingList implementation to run a search on any of the element's properties
+ ///
+ protected override int FindCore(PropertyDescriptor property, object key) {
+ int count = this.Count;
+ for(int index = 0; index < count; ++index) {
+ TElement element = this[index];
+ if(property.GetValue(element).Equals(key)) {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ /// Cached property comparers, created for each element property as needed
+ private readonly Dictionary comparers;
+ /// Whether the binding list is currently sorted
+ private bool isSorted;
+ /// Direction in which the binding list is currently sorted
+ private ListSortDirection listSortDirection;
+ /// Descriptor for the property by which the binding list is currently sorted
+ private PropertyDescriptor propertyDescriptor;
+
+ }
+
+} // namespace Nuclex.Support.Collections