Search code examples
c#imagesortingdatagridview.net-2.0

Sort DataGridView column of type Image


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).

Question: How do I make my image column sortable?

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.


Solution

  • 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.

    Results

    Code:

    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; }
                    }
                }
        }
    

    Object Class:

        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));
            }
        }
    

    SortableBindingList

        /// <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
            }
        }
    }