Skip to content

Instantly share code, notes, and snippets.

@jsmarble
Last active April 30, 2025 16:07
Show Gist options
  • Save jsmarble/a5bf0ededd2df968ea189fea81233472 to your computer and use it in GitHub Desktop.
Save jsmarble/a5bf0ededd2df968ea189fea81233472 to your computer and use it in GitHub Desktop.
Custom Sorting of BindingList<T>
public class CustomBindingList<T> : BindingList<T>
{
private bool isSorting;
/// <summary>
/// Raised when the list is sorted.
/// </summary>
public event EventHandler Sorted;
public CustomBindingList()
: this(null) { }
public CustomBindingList(IEnumerable<T> contents)
: this(contents, null) { }
public CustomBindingList(IEnumerable<T> contents, ISortComparer<T> comparer)
{
if (contents != null)
AddRange(contents);
if (comparer == null)
SortComparer = new GenericSortComparer<T>();
else
SortComparer = comparer;
}
#region Properties
private ISortComparer<T> sortComparer;
public ISortComparer<T> SortComparer
{
get { return sortComparer; }
set
{
if (value == null)
throw new ArgumentNullException("SortComparer", "Value cannot be null.");
sortComparer = value;
}
}
private bool isSorted;
protected override bool IsSortedCore
{
get { return isSorted; }
}
protected override bool SupportsSortingCore
{
get { return true; }
}
private ListSortDirection sortDirection;
protected override ListSortDirection SortDirectionCore
{
get { return sortDirection; }
}
private PropertyDescriptor sortProperty;
protected override PropertyDescriptor SortPropertyCore
{
get { return sortProperty; }
}
#endregion
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
if (prop == null)
return;
isSorting = true;
sortDirection = direction;
sortProperty = prop;
this.SortComparer.SortProperty = prop;
this.SortComparer.SortDirection = direction;
((List<T>)this.Items).Sort(this.SortComparer);
isSorted = true;
isSorting = false;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, 0));
OnSorted(null, new EventArgs());
}
protected override void RemoveSortCore()
{
throw new NotSupportedException();
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (!isSorting)
base.OnListChanged(e);
}
protected virtual void OnSorted(object sender, EventArgs e)
{
if (Sorted != null)
Sorted(sender, e);
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (!isSorting)
this.ApplySortCore(this.SortPropertyCore, this.SortDirectionCore);
}
protected override void SetItem(int index, T item)
{
base.SetItem(index, item);
if (!isSorting)
this.ApplySortCore(this.SortPropertyCore, this.SortDirectionCore);
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
if (!isSorting)
this.ApplySortCore(this.SortPropertyCore, this.SortDirectionCore);
}
protected override void ClearItems()
{
base.ClearItems();
}
public void AddRange(IEnumerable<T> items)
{
if (items != null)
foreach (T item in items)
this.Items.Add(item);
}
}
public class GenericSortComparer<T> : ISortComparer<T>
{
public GenericSortComparer()
{
}
public GenericSortComparer(string sortProperty, ListSortDirection sortDirection)
: this(TypeDescriptor.GetProperties(typeof(T)).Find(sortProperty, true), sortDirection)
{
}
public GenericSortComparer(PropertyDescriptor sortProperty, ListSortDirection sortDirection)
{
this.SortDirection = sortDirection;
this.SortProperty = sortProperty;
}
public PropertyDescriptor SortProperty { get; set; }
public ListSortDirection SortDirection { get; set; }
public int Compare(T x, T y)
{
if (this.SortProperty == null)
return 0;
IComparable obj1 = this.SortProperty.GetValue(x) as IComparable;
IComparable obj2 = this.SortProperty.GetValue(y) as IComparable;
if (obj1 == null || obj2 == null)
return 0;
if (this.SortDirection == ListSortDirection.Ascending)
return (obj1.CompareTo(obj2));
else
return (obj2.CompareTo(obj1));
}
}
public interface ISortComparer<T> : IComparer<T>
{
PropertyDescriptor SortProperty { get; set; }
ListSortDirection SortDirection { get; set; }
}
@softhor
Copy link

softhor commented Apr 29, 2025

Could I suggest an improvement?

Replacing the method Compare in GenericSortComparer the sort behaves like ORDER BY in SQL: the items are sorted putting on top the once with null values if in ascending order or at bottom if in descending order.

public int Compare(T x, T y)
{
	if (SortProperty == null)
		return 0;

	IComparable obj1 = SortProperty.GetValue(x) as IComparable;
	IComparable obj2 = SortProperty.GetValue(y) as IComparable;

	if (obj1 == null && obj2 != null)
	{
		return SortDirection == ListSortDirection.Ascending ? -1 : 1;
	}

	if (obj1 != null && obj2 == null)
	{
		return SortDirection == ListSortDirection.Ascending ? 1 : -1;
	}

	if (obj1 == null && obj2 == null)
		return 0;

	if (SortDirection == ListSortDirection.Ascending)
		return obj1.CompareTo(obj2);
	else
		return obj2.CompareTo(obj1);
}

@jsmarble
Copy link
Author

jsmarble commented Apr 30, 2025

Interesting. I suppose that could be a matter of what experience you have with different sorting behaviors. I used the Object.CompareTo in order to get the native object's compare logic. I would imagine if the .NET devs wanted to sort like SQL ORDER BY that they would have returned the value similar to your suggestion. It does make one wonder why they chose this direction, but at the same time, I tend to think following their logic baked into the framework would be preferable to a custom override of behavior. Thanks for the discourse!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment