By default, DataGridViewColumn.ValueType
Image cannot be sorted.
That's understandable, but my images can be sorted in a logical way.
I am showing one of three images depending on some state (Check mark, X, or Question mark).
This question: c# window form DataTable with Image column Sorting comes close, however it is using DataTable, not DataGridView, and more importantly I am restricted to .NET 2 which does not have Linq.
This question: How to create a comparable Image is probably the best solution, but is uses Func which is only available from .NET 3.5 up.
Another aspect to this is that I'm using a BindingList.
More specifically, this SortableBindingList.
The solution I ended up using was to redirect the sort to a hidden column that has values corresponding to the images in a meaningful way.
✔ = 1
? = 2
✘ = 3
I used the ColumnHeaderMouseClick
event to put my logic in and made sure to show the sort glyph for the image column as well.
namespace SortImagesInDGV
{
public partial class Form1 : Form
{
SortableBindingList<CustomObject> mySortableBindingList;
Image C32 = SortImagesInDGV.Properties.Resources.C_32.ToBitmap();
Image X32 = SortImagesInDGV.Properties.Resources.X_32.ToBitmap();
Image Q32 = SortImagesInDGV.Properties.Resources.Q_32.ToBitmap();
// used to keep track of sort direction
bool SortingFlipFlop = true;
public Form1()
{
InitializeComponent();
mySortableBindingList = new SortableBindingList<CustomObject>();
mySortableBindingList.Add(new CustomObject("c mark", 1, C32));
mySortableBindingList.Add(new CustomObject("x mark", 3, X32));
mySortableBindingList.Add(new CustomObject("q mark", 2, Q32));
mySortableBindingList.Add(new CustomObject("cross mark", 3, X32));
mySortableBindingList.Add(new CustomObject("check mark", 1, C32));
mySortableBindingList.Add(new CustomObject("question mark", 2, Q32));
dataGridView1.DataSource = mySortableBindingList;
// Sorting image with this event
dataGridView1.ColumnHeaderMouseClick += new DataGridViewCellMouseEventHandler(gridViewData_ColumnHeaderMouseClick);
// Must explicitly set the image column as sortable
dataGridView1.Columns["AnImage"].SortMode = DataGridViewColumnSortMode.Automatic;
// Hide the number "key" column
dataGridView1.Columns["ANumber"].Visible = false;
}
void gridViewData_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (dataGridView1.Columns[e.ColumnIndex].Name == "AnImage")
{
// Change the sort direction each time the column header for image is clicked
ListSortDirection Direction;
if (SortingFlipFlop) { Direction = ListSortDirection.Ascending; SortingFlipFlop = false; }
else { Direction = ListSortDirection.Descending; SortingFlipFlop = true; }
// Perform the sort on the number / 'key' column
dataGridView1.Sort(dataGridView1.Columns["ANumber"], Direction);
// Show the sorting glyph in the image column
if (Direction == ListSortDirection.Ascending)
{ dataGridView1.Columns["AnImage"].HeaderCell.SortGlyphDirection = SortOrder.Descending; }
else if (Direction == ListSortDirection.Descending)
{ dataGridView1.Columns["AnImage"].HeaderCell.SortGlyphDirection = SortOrder.Ascending; }
else { dataGridView1.Columns["AnImage"].HeaderCell.SortGlyphDirection = SortOrder.None; }
}
}
}
public class CustomObject : INotifyPropertyChanged
{
private string _someText; private int _aNumber; private Image _anImage;
public event PropertyChangedEventHandler PropertyChanged;
public CustomObject(string sometext, int anumber, Image animage)
{ _someText = sometext; _aNumber = anumber; _anImage = animage; }
[DisplayName("Some Text")]
public string SomeText { get { return _someText; }
set { _someText = value; this.NotifyPropertyChanged("SomeText"); }
}
public int ANumber { get { return _aNumber; }
set { _aNumber = value; this.NotifyPropertyChanged("ANumber"); }
}
[DisplayName("My Image")]
public Image AnImage { get { return _anImage; }
set { _anImage = value; this.NotifyPropertyChanged("AnImage"); }
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Provides a generic collection that supports data binding and additionally supports sorting.
/// See http://msdn.microsoft.com/en-us/library/ms993236.aspx
/// If the elements are IComparable it uses that; otherwise compares the ToString()
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
public class SortableBindingList<T> : BindingList<T> where T : class
{
private bool _isSorted;
private ListSortDirection _sortDirection = ListSortDirection.Ascending;
private PropertyDescriptor _sortProperty;
/// <summary>
/// Initializes a new instance of the <see cref="SortableBindingList{T}"/> class. </summary>
public SortableBindingList() { }
/// <summary>
/// Initializes a new instance of the <see cref="SortableBindingList{T}"/> class. </summary>
/// <param name="list">An <see cref="T:System.Collections.Generic.IList`1" /> of items to be contained in the <see cref="T:System.ComponentModel.BindingList`1" />.</param>
public SortableBindingList(IList<T> list) : base(list) { }
/// <summary>
/// Gets a value indicating whether the list supports sorting. </summary>
protected override bool SupportsSortingCore { get { return true; } }
/// <summary>
/// Gets a value indicating whether the list is sorted. </summary>
protected override bool IsSortedCore { get { return _isSorted; } }
/// <summary>
/// Gets the direction the list is sorted. </summary>
protected override ListSortDirection SortDirectionCore { get { return _sortDirection; } }
public ListSortDirection mySortDirection { get { return _sortDirection; } }
/// <summary>
/// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null </summary>
protected override PropertyDescriptor SortPropertyCore { get { return _sortProperty; } }
/// <summary>
/// Removes any sort applied with ApplySortCore if sorting is implemented </summary>
protected override void RemoveSortCore() {
_sortDirection = ListSortDirection.Ascending; _sortProperty = null; _isSorted = false;
}
/// <summary>
/// Sorts the items if overridden in a derived class </summary>
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) {
_sortProperty = prop; _sortDirection = direction;
List<T> list = Items as List<T>;
if (list == null) return;
list.Sort(Compare); _isSorted = true;
//fire an event that the list has been changed.
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
private int Compare(T lhs, T rhs) {
var result = OnComparison(lhs, rhs);
//invert if descending
if (_sortDirection == ListSortDirection.Descending) result = -result;
return result;
}
private int OnComparison(T lhs, T rhs) {
object lhsValue = null; if (lhs != null) { lhsValue = _sortProperty.GetValue(lhs); }
object rhsValue = null; if (rhs != null) { rhsValue = _sortProperty.GetValue(rhs); }
if (lhsValue == null) { return (rhsValue == null) ? 0 : -1; }
if (rhsValue == null) { return 1; } //first has value, second doesn't
if (lhsValue is IComparable) { return ((IComparable)lhsValue).CompareTo(rhsValue); }
if (lhsValue.Equals(rhsValue)) { return 0; } //both are the same
return lhsValue.ToString().CompareTo(rhsValue.ToString()); //not comparable, compare ToString
}
}
}