#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
public 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