views:

4487

answers:

7

I am setting-up my DataGridView like this:

        jobs = new List<DisplayJob>();

        uxJobList.AutoGenerateColumns = false;
        jobListBindingSource.DataSource = jobs;
        uxJobList.DataSource = jobListBindingSource;

        int newColumn;
        newColumn = uxJobList.Columns.Add("Id", "Job No.");
        uxJobList.Columns[newColumn].DataPropertyName = "Id";
        uxJobList.Columns[newColumn].DefaultCellStyle.Format = Global.JobIdFormat;
        uxJobList.Columns[newColumn].DefaultCellStyle.Font = new Font(uxJobList.DefaultCellStyle.Font, FontStyle.Bold);
        uxJobList.Columns[newColumn].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
        uxJobList.Columns[newColumn].Width = 62;
        uxJobList.Columns[newColumn].Resizable = DataGridViewTriState.False;
        uxJobList.Columns[newColumn].SortMode = DataGridViewColumnSortMode.Automatic;
        :
        :

where the DisplayJob class looks like:

    public class DisplayJob
{
    public DisplayJob(int id)
    {
        Id = id;
    }

    public DisplayJob(JobEntity job)
    {
        Id = job.Id;
        Type = job.JobTypeDescription;
        CreatedAt = job.CreatedAt;
        StartedAt = job.StartedAt;
        ExternalStatus = job.ExternalStatus;
        FriendlyExternalStatus = job.FriendlyExternalStatus;
        ExternalStatusFriendly = job.ExternalStatusFriendly;
        CustomerName = job.Customer.Name;
        CustomerKey = job.Customer.CustomerKey;
        WorkAddress = job.WorkAddress;
        CreatedBy = job.CreatedBy;
        CancelledAt = job.CancelledAt;
        ClosedAt = job.ClosedAt;
        ReasonWaiting = job.ReasonWaiting;
        CancelledBy = job.CancelledBy;
        CancelledReason = job.CancelledReason;
        DisplayCreator = Global.GetDisplayName(CreatedBy);
        ActionRedoNeeded = job.ActionRedoNeeded;
        if (job.Scheme != null)
        {
            SchemeCode = job.Scheme.Code;
        }

    }

    public int Id { get; private set; }
    public string Type { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? StartedAt { get; private set; }
    public string ExternalStatus { get; private set; }
    public string FriendlyExternalStatus { get; private set; }
    public string ExternalStatusFriendly { get; private set; }
    public string CustomerName { get; private set; }
    public string CustomerKey { get; private set; }
    public string WorkAddress { get; private set; }
    public string CreatedBy { get; private set; }
    public DateTime? CancelledAt { get; private set; }
    public DateTime? ClosedAt { get; private set; }
    public string CancelledBy { get; private set; }
    public string ReasonWaiting { get; private set; }
    public string DisplayCreator { get; private set; }
    public string CancelledReason { get; private set; }
    public string SchemeCode { get; private set; }
    public bool ActionRedoNeeded { get; private set; }
}

However the column sorting does not work. What is the best way to get this working?

A: 

I believe that your class must implement the IComparable interface.

Hope it helps,

Bruno Figueiredo

Bruno Shine
+2  A: 

One of the easiest ways is to use the BindingListView class to wrap your list of DisplayJobs. The class implements some of the required interfaces that enable sorting and filtering in a DataGridView. That's the quick way. It works pretty well, though -- the only caveat is that if you cast things out of the DataGridView you need to cast to the wrapper object (ObjectView) instead of the actual item (DisplayJob).

The less lazy way is to create a custom collection time that implements IBindingList, implementing the sort methods there.

Nicholas Piasecki
+7  A: 

If you want to support sorting and searching on the collection, all it takes it to derive a class from your BindingList parameterized type, and override a few base class methods and properties.

The best way is to extend the BindingList and do those following things:

protected override bool SupportsSearchingCore
{
    get
    {
        return true;
    }
}

protected override bool SupportsSortingCore
{
    get { return true; }
}

You will also need to implement the sort code:

ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;

protected override void ApplySortCore(PropertyDescriptor prop, 
    ListSortDirection direction)
{
    sortedList = new ArrayList();

    // Check to see if the property type we are sorting by implements
    // the IComparable interface.
    Type interfaceType = prop.PropertyType.GetInterface("IComparable");

    if (interfaceType != null)
    {
        // If so, set the SortPropertyValue and SortDirectionValue.
        sortPropertyValue = prop;
        sortDirectionValue = direction;

        unsortedItems = new ArrayList(this.Count);

        // Loop through each item, adding it the the sortedItems ArrayList.
        foreach (Object item in this.Items) {
            sortedList.Add(prop.GetValue(item));
            unsortedItems.Add(item);
        }
        // Call Sort on the ArrayList.
        sortedList.Sort();
        T temp;

        // Check the sort direction and then copy the sorted items
        // back into the list.
        if (direction == ListSortDirection.Descending)
            sortedList.Reverse();

        for (int i = 0; i < this.Count; i++)
        {
            int position = Find(prop.Name, sortedList[i]);
            if (position != i) {
                temp = this[i];
                this[i] = this[position];
                this[position] = temp;
            }
        }

        isSortedValue = true;

        // Raise the ListChanged event so bound controls refresh their
        // values.
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }
    else
        // If the property type does not implement IComparable, let the user
        // know.
        throw new NotSupportedException("Cannot sort by " + prop.Name +
            ". This" + prop.PropertyType.ToString() + 
            " does not implement IComparable");
}

If you need more information you can always go there and get all explication about how to extend the binding list.

Daok
+4  A: 

Daok's solution is the right one. It's also very often more work than it's worth.

The lazy man's way to get the functionality you want is to create and populate a DataTable off of your business objects, and bind the DataGridView to that.

There are a lot of use cases that this approach won't handle (like, editing), and it obviously wastes time and space. As I said, it's lazy.

But it's easy to write, and the resulting code is a damn sight less mysterious than an implementation of IBindingList.

Also, you're already writing a lot of the code anyway, or similar code at least: the code you write to define the DataTable frees you from having to write code to create the columns of the DataGridView, since the DataGridView will construct its columns off of the DataTable when you bind it.

Robert Rossney
+1  A: 

The MS article suggested by Daok got me on the right track, but I wasn't satisfied with MSs implementation of SortableSearchableList. I find that implementation very strange and it didn't work well when there are duplicate values in a column. It also doesn't override IsSortedCore, which seems required by the DataGridView. If IsSortedCore is not overriden, the search glyph doesn't appear and toggling between ascending and descending doesn't work.

See my version of SortableSearchableList below. In ApplySortCore() it sorts using a Comparison delegate set to an anonymous method. This version also supports setting custom comparisons for a particular property, which can be added by a derived class using AddCustomCompare().

I'm not sure if the copyright notice still applies, but I just left it in.

//---------------------------------------------------------------------
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Collections;

namespace SomethingSomething
{
    /// <summary>
    /// Supports sorting of list in data grid view.
    /// </summary>
    /// <typeparam name="T">Type of object to be displayed in data grid view.</typeparam>
    public class SortableSearchableList<T> : BindingList<T>
    {
        #region Data Members

        private ListSortDirection _sortDirectionValue;
        private PropertyDescriptor _sortPropertyValue = null;

        /// <summary>
        /// Dictionary from property name to custom comparison function.
        /// </summary>
        private Dictionary<string, Comparison<T>> _customComparisons = new Dictionary<string, Comparison<T>>();

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        public SortableSearchableList()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Indicates if sorting is supported.
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Indicates if list is sorted.
        /// </summary>
        protected override bool IsSortedCore
        {
            get
            {
                return _sortPropertyValue != null;
            }
        }

        /// <summary>
        /// Indicates which property the list is sorted.
        /// </summary>
        protected override PropertyDescriptor SortPropertyCore
        {
            get
            {
                return _sortPropertyValue;
            }
        }

        /// <summary>
        /// Indicates in which direction the list is sorted on.
        /// </summary>
        protected override ListSortDirection SortDirectionCore
        {
            get
            {
                return _sortDirectionValue;
            }
        }

        #endregion

        #region Methods       

        /// <summary>
        /// Add custom compare method for property.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="compareProperty"></param>
        protected void AddCustomCompare(string propertyName, Comparison<T> comparison)
        {
            _customComparisons.Add(propertyName, comparison);
        }

        /// <summary>
        /// Apply sort.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="direction"></param>
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            Comparison<T> comparison;
            if (!_customComparisons.TryGetValue(prop.Name, out comparison))
            {
                // Check to see if the property type we are sorting by implements
                // the IComparable interface.
                Type interfaceType = prop.PropertyType.GetInterface("IComparable");
                if (interfaceType != null)
                {
                    comparison = delegate(T t1, T t2)
                        {
                            IComparable val1 = (IComparable)prop.GetValue(t1);
                            IComparable val2 = (IComparable)prop.GetValue(t2);
                            return val1.CompareTo(val2);
                        };
                }
                else
                {
                    // Last option: convert to string and compare.
                    comparison = delegate(T t1, T t2)
                        {
                            string val1 = prop.GetValue(t1).ToString();
                            string val2 = prop.GetValue(t2).ToString();
                            return val1.CompareTo(val2);
                        };
                }
            }

            if (comparison != null)
            {
                // If so, set the SortPropertyValue and SortDirectionValue.
                _sortPropertyValue = prop;
                _sortDirectionValue = direction;

                // Create sorted list.
                List<T> _sortedList = new List<T>(this);                   
                _sortedList.Sort(comparison);

                // Reverse order if needed.
                if (direction == ListSortDirection.Descending)
                {
                    _sortedList.Reverse();
                }

                // Update list.
                int count = this.Count;
                for (int i = 0; i < count; i++)
                {
                    this[i] = _sortedList[i];
                }

                // Raise the ListChanged event so bound controls refresh their
                // values.
                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
            }
        }

        // Method below was in the original implementation from MS. Don't know what it's for.
        // -- Martijn Boeker, Jan 21, 2010

        //protected override void RemoveSortCore()
        //{
        //    //int position;
        //    //object temp;
        //    //// Ensure the list has been sorted.
        //    //if (unsortedItems != null)
        //    //{
        //    //    // Loop through the unsorted items and reorder the
        //    //    // list per the unsorted list.
        //    //    for (int i = 0; i < unsortedItems.Count; )
        //    //    {
        //    //        position = this.Find(SortPropertyCore.Name,
        //    //            unsortedItems[i].GetType().
        //    //            GetProperty(SortPropertyCore.Name).
        //    //            GetValue(unsortedItems[i], null));
        //    //        if (position >= 0 && position != i)
        //    //        {
        //    //            temp = this[i];
        //    //            this[i] = this[position];
        //    //            this[position] = (T)temp;
        //    //            i++;
        //    //        }
        //    //        else if (position == i)
        //    //            i++;
        //    //        else
        //    //            // If an item in the unsorted list no longer exists, delete it.
        //    //            unsortedItems.RemoveAt(i);
        //    //    }
        //    //    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        //    //}
        //}

        /// <summary>
        /// Ability to search an item.
        /// </summary>
        protected override bool SupportsSearchingCore
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Finds an item in the list.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        protected override int FindCore(PropertyDescriptor prop, object key)
        {
            // Implementation not changed from MS example code.

            // Get the property info for the specified property.
            PropertyInfo propInfo = typeof(T).GetProperty(prop.Name);
            T item;

            if (key != null)
            {
                // Loop through the the items to see if the key
                // value matches the property value.
                for (int i = 0; i < Count; ++i)
                {
                    item = (T)Items[i];
                    if (propInfo.GetValue(item, null).Equals(key))
                        return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// Finds an item in the list.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private int Find(string property, object key)
        {
            // Implementation not changed from MS example code.

            // Check the properties for a property with the specified name.
            PropertyDescriptorCollection properties =
                TypeDescriptor.GetProperties(typeof(T));
            PropertyDescriptor prop = properties.Find(property, true);

            // If there is not a match, return -1 otherwise pass search to
            // FindCore method.
            if (prop == null)
                return -1;
            else
                return FindCore(prop, key);
        }

        #endregion
    }
}
Martijn Boeker
This solution works well, thank you
marco.ragogna
A: 

Martijn excelent code but only one detail u need to validate null cells or empty :)

if (!_customComparisons.TryGetValue(prop.Name, out comparison))
{
    // Check to see if the property type we are sorting by implements
    // the IComparable interface.
    Type interfaceType = prop.PropertyType.GetInterface("IComparable");
    if (interfaceType != null)
    {
        comparison = delegate(T t1, T t2)
            {
                IComparable val1 = (IComparable)prop.GetValue(t1) ?? "";
                IComparable val2 = (IComparable)prop.GetValue(t2) ?? "";
                return val1.CompareTo(val2);
            };
    }
    else
    {
        // Last option: convert to string and compare.
        comparison = delegate(T t1, T t2)
            {
                string val1 = (prop.GetValue(t1) ?? "").ToString();
                string val2 = (prop.GetValue(t2) ?? "").ToString();
                return val1.CompareTo(val2);
            };
    }
}

That's all luck

CXRom
A: 

I'd recommend replacing:

jobs = new List<DisplayJob>();

with:

jobs = new SortableBindingList<DisplayJob>();

The code for SortableBindingList is here: http://www.timvw.be/presenting-the-sortablebindinglistt/

I've used code based on this in production without any problems. It's only limitation is that it is not a stable sort.

If you want the sort to be stable, replace:

itemsList.Sort(delegate(T t1, T t2)
{
    object value1 = prop.GetValue(t1);
    object value2 = prop.GetValue(t2);

    return reverse * Comparer.Default.Compare(value1, value2);
});

with an insertion sort:

int j;
T index;
for (int i = 0; i < itemsList.Count; i++)
{
    index = itemsList[i];
    j = i;

    while ((j > 0) && (reverse * Comparer.Default.Compare(prop.GetValue(itemsList[j - 1]), prop.GetValue(index)) > 0))
    {
        itemsList[j] = itemsList[j - 1];
        j = j - 1;
    }

    itemsList[j] = index;
}
Joe H