#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