Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Languages / C# / March 2008

Tip: Looking for answers? Try searching our database.

Sorting Bindinglist based DataGridView

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Andrus - 22 Mar 2008 21:22 GMT
I have 3.5 WinForms DataGridView whose DataSource is BindingList<TEntity>.
I need to sort when user clicks in column header.

I tried MSDN sample but

Sort(newColumn, direction);

causes exception:

DataGridView control cannot be sorted if it is bound to an IBindingList that
does not support sorting.

How to allow user to sort in this DataGridView ?

Andrus.
RobinS - 23 Mar 2008 06:35 GMT
>I have 3.5 WinForms DataGridView whose DataSource is BindingList<TEntity>.
> I need to sort when user clicks in column header.
[quoted text clipped - 11 lines]
>
> Andrus.

Assuming that TEntity is a class, here's an example of what you need to do.
This code goes in the class that is the BindingList<TEntity>.

In this example, BusObj the business object, BusObjList is the business
object list.
I am binding it to a DataGridView with a BindingSource.
This is the code required to get it to sort.

Note: I am implementing IRaiseItemChangedEvents because my BusObj implements
IEditableObject,
and IRaiseItemChangedEvents tells the binding source when they add a a row,
then change their mind and undo it. (In case you were wondering.)

The sort code is from Brian Noyes' excellent book on Data Binding.
It uses a generic comparer; I have included it at the bottom.
His book also includes methods to remove the sort and return to the original
list order,
but I didn't care about that, so I haven't implemented it here.

With a DataGridView, the sorting glyph will show up when it sorts the
column.

public class BusObjList : BindingList<BusObj>, IRaiseItemChangedEvents
{

       //properties and methods of your business object list class

       //these are used to implement the sorting for the BindingList
       private bool m_Sorted = false;
       private ListSortDirection m_SortDirection =
ListSortDirection.Ascending;
       private PropertyDescriptor m_SortProperty = null;

       //Sorting Code
       //To perform the sort of our BindingList<T> class, we have to
provide the overrides
       //of all the sort-related methods and properties from the base
class.

       /// <summary>
       /// Override the value and set it to true so sorting will work on
the list.
       /// </summary>
       protected override bool SupportsSearchingCore
       {
           get
           {
               return true;
           }
       }

       /// <summary>
       /// Override the value and set it to true so sorting will work on
the list.
       /// </summary>
       protected override bool SupportsSortingCore
       {
           get
           {
               return true;
           }
       }

       /// <summary>
       /// Return the value retained locally. Tells if the list is sorted
or not.
       /// </summary>
       protected override bool IsSortedCore
       {
           get
           {
               return m_Sorted;
           }
       }

       /// <summary>
       /// Return the value retained locally. Tells which direction the
list is sorted.
       /// </summary>
       protected override ListSortDirection SortDirectionCore
       {
           get
           {
               return m_SortDirection;
           }
       }

       /// <summary>
       /// Return the value retained locally. Tells which property the list
is sorted on.
       /// </summary>
       protected override PropertyDescriptor SortPropertyCore
       {
           get
           {
               return m_SortProperty;
           }
       }

       /// <summary>
       /// Sets the properties when called by the base class in response to
the ApplySort call.
       /// Delegates to a helper method (ApplySortInternal) to do most of
the work of the sorting.
       /// </summary>
       /// <param name="prop"></param>
       /// <param name="direction"></param>
       protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
       {
           m_SortDirection = direction;
           m_SortProperty = prop;
           BOSortComparer<BusObj> comparer = new
BOSortComparer<BusObj>(prop, direction);
           ApplySortInternal(comparer);
      }

       /// <summary>
       /// Helper class to do the actual sorting work.
       /// </summary>
       /// <param name="comparer"></param>
       private void ApplySortInternal(BOSortComparer<BusObj> comparer)
       {
           //this causes the items in the collection maintained by the base
class to be sorted
           //  according to the criteria provided to the BOSortComparer
class.
           List<BusObj> listRef = this.Items as List<BusObj>;
           if (listRef == null)
               return;

           //let List<T> do the actual sorting based on your comparer
           listRef.Sort(comparer);
           m_Sorted = true;
           //fire an event through a call to the base class OnListChanged
method indicating
           //  that the list has been changed.
           //Use 'reset' because it's likely that most members have been
moved around.
           OnListChanged(new
ListChangedEventArgs(ListChangedType.Reset, -1));
       }

------------------------------------------------------------------------------
This is the generic sort comparer that I am using.

   /// <summary>
   /// Generic SortComparer classes. Used to sort a list of objects by any
property.
   /// </summary>
   /// <typeparam name="T"></typeparam>
   class BOSortComparer<T> : IComparer<T>
   {
       private PropertyDescriptor m_PropDesc = null;
       private ListSortDirection m_Direction = ListSortDirection.Ascending;

       public BOSortComparer(PropertyDescriptor propDesc, ListSortDirection
direction)
       {
           m_PropDesc = propDesc;
           m_Direction = direction;
       }

       int IComparer<T>.Compare(T x, T y)
       {
           object xValue = m_PropDesc.GetValue(x);
           object yValue = m_PropDesc.GetValue(y);
           return CompareValues(xValue, yValue, m_Direction);
       }

       private int CompareValues(object xValue, object yValue,
ListSortDirection direction)
       {
           int retValue = 0;
           if (xValue is IComparable) //can ask the x value
           {
               retValue = ((IComparable)xValue).CompareTo(yValue);
           }
           else if (yValue is IComparable) //can ask the y value
           {
               retValue = ((IComparable)yValue).CompareTo(xValue);
           }
           //not comparable, compare string representations
           else if (!xValue.Equals(yValue))
           {
               retValue = xValue.ToString().CompareTo(yValue.ToString());
           }
           if (direction == ListSortDirection.Ascending)
               return retValue;
           else
               return retValue * -1;

       }

   }

------------------------------------

That should do it for you. Let me know if you have any questions.

RobinS.
GoldMail.com
Andrus - 23 Mar 2008 11:09 GMT
RobinS,

> Assuming that TEntity is a class, here's an example of what you need to
> do. This code goes in the class that is the BindingList<TEntity>.

Thank you. I have custom Combobox cell in DataGridView

class ComboBoxCell : DataGridViewComboBoxCell {

protected override object GetFormattedValue(object value,
           int rowIndex, ref DataGridViewCellStyle cellStyle,
           TypeConverter valueTypeConverter,
           TypeConverter formattedValueTypeConverter,
          DataGridViewDataErrorContexts context) {
....
return base.GetFormattedValue(value, rowIndex, ref cellStyle,
valueTypeConverter, formattedValueTypeConverter, context);
}

Using your class causes exception when form is opened  in
base.GetFormattedValue()

System.NotSupportedException was unhandled
 Message="Specified method is not supported."
 Source="System"
 StackTrace:
      at System.ComponentModel.BindingList`1.FindCore(PropertyDescriptor
prop, Object key)
      at
System.ComponentModel.BindingList`1.System.ComponentModel.IBindingList.Find(PropertyDescriptor
prop, Object key)
      at
System.Windows.Forms.DataGridViewComboBoxCell.ItemFromComboBoxDataSource(PropertyDescriptor
property, Object key)
      at
System.Windows.Forms.DataGridViewComboBoxCell.LookupDisplayValue(Int32
rowIndex, Object value, Object& displayValue)
      at
System.Windows.Forms.DataGridViewComboBoxCell.GetFormattedValue(Object
value, Int32 rowIndex, DataGridViewCellStyle& cellStyle, TypeConverter
valueTypeConverter, TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
....

If I use normal BindingList class, grid displays without error.

How to fix ?

Linq as OrderBy method.
I'snt it more resonable to use its OrderBy() method instead of re-inventing
wheel by creating own comparer ?
This would reduce the amount of code a lot.
If yes, how to implement this with les code ?

Andrus.
Marc Gravell - 23 Mar 2008 14:31 GMT
> If I use normal BindingList class, grid displays without error.
> How to fix ?
I don't know why it behaves differently to a regular BindingList<T> -
but note that it isn't that hard to implement IBindingList.Find - it
might even be as simple as below (from a BindingList<T> subclass)

> I'snt it more resonable to use its OrderBy() method instead of re-inventing
> wheel by creating own comparer ?
No; the standard Enumerable.OrderBy returns a *different* list, where-
as we want to sort the one in-place. I have played with some IList/
IList<T> extension methods for in-place, and I believe that Jon has
also taken a look, but standard Enumerable isn't an option here. I
have posted several sortable BindingList<T> implementations over the
years - not sure if this one is any beter / worse (I haven't looked in
great detail) - but there isn't that much available to you.
List<T>.Sort with a comparer is a reasonable option.

Marc

Find suggeston (not tested):

       protected override bool SupportsSearchingCore
       {
           get
           {
               return true;
           }
       }
       protected override int FindCore(PropertyDescriptor prop,
object key)
       {
           if(prop == null) throw new ArgumentNullException("prop");
           for (int i = 0; i < Count; i++)
           {
               object item = this[i];
               if (item != null && object.Equals(key,
prop.GetValue(item))) {
                 return i;
               }
           }
           return -1;
       }
Andrus - 24 Mar 2008 00:30 GMT
Marc,

> I don't know why it behaves differently to a regular BindingList<T> -
> but note that it isn't that hard to implement IBindingList.Find - it
> might even be as simple as below (from a BindingList<T> subclass)

I'm sorry I don't  understand this code.

If I create SortableBindingList<T>: BindingList<T> { },
add those two methods to this class,
instantiate it using List<T> as constructor parameter
and pass result list to DataGridView DataSource

should sorting work OK in this case ?

>> I'snt it more resonable to use its OrderBy() method instead of
>> re-inventing
[quoted text clipped - 7 lines]
> great detail) - but there isn't that much available to you.
> List<T>.Sort with a comparer is a reasonable option.

Isn't  it more reasonable to replace DataGridView DataSource with new sorted
BindingList to perform sorting?
In this case it is possible to use dynamic OrderBy method probably.

Andrus.
Marc Gravell - 24 Mar 2008 10:49 GMT
> Isn't  it more reasonable to replace DataGridView DataSource with new sorted
> BindingList to perform sorting?

Not if you want it integrated with DGV and other binding controls.

But yes: another viable option would be to track the sort manually and
change the entire binding each time.

Marc
RobinS - 24 Mar 2008 06:48 GMT
To do it in less code, put your data in a DataTable and bind it to the DGV.
Then it will sort innately. If you want to use business objects, you have to
override all of the sort methods for the BindingSource. The other option is
to put selection criteria (combobox? button?) on the screen for the user to
select to sort the list, and you sort it and rebind it when they change the
selection. Frankly, this solution is cleaner, and works the way a user
expects it to do.

For your GetFormattedValue, I would implement the Find method as described
by the other user.

RobinS.

> RobinS,
>
[quoted text clipped - 52 lines]
>
> Andrus.
Andrus - 24 Mar 2008 12:00 GMT
RobinS,

> For your GetFormattedValue, I would implement the Find method as described
> by the other user.

Thank you. After adding Marc Find() method excaption does not more occur.
Can you confirm that the code you posted was incomplete: it did'nt contain
Find() method.

Andrus.
RobinS - 26 Mar 2008 08:23 GMT
It did not include the Find method because I didn't need a find method. You
asked me how to SORT, not how to FIND.  :-)

RobinS.

> RobinS,
>
[quoted text clipped - 7 lines]
>
> Andrus.
Andrus - 26 Mar 2008 15:58 GMT
RobinS,

> It did not include the Find method because I didn't need a find method.
> You asked me how to SORT, not how to FIND.  :-)

I don't use Find() in my code.

DataGridView requires Find() method  implementation to show formatted value,
no idea why.

Andrus.
RobinS - 27 Mar 2008 07:18 GMT
> RobinS,
>
[quoted text clipped - 7 lines]
>
> Andrus.

Me neither. It's weird, but at least it's weird and fixable. :-)
RobinS.
Andrus - 24 Mar 2008 12:11 GMT
RobinS,

This code does not work with null values.
In   int CompareValues(object xValue, object yValue, ListSortDirection
direction)

at line

else if (!xValue.Equals(yValue)) {

I got NullReferenceExcpetion since xValue is null.

Should I re-invent wheel by fixing this myself or is there solution which
work with null values also ?

Andrus.
Marc Gravell - 24 Mar 2008 23:29 GMT
> is there solution which work with null values also ?

int retValue= Comparer.Compare(xValue, yValue);
return direction == ListSortDirection.Ascending ? retValue : -
retValue;

Marc
Andrus - 25 Mar 2008 20:39 GMT
>> is there solution which work with null values also ?
>
> int retValue= Comparer.Compare(xValue, yValue);
> return direction == ListSortDirection.Ascending ? retValue : -
> retValue;

Marc,

thank you.

int retValue= Comparer.Compare(xValue, yValue);

causes compile time error

Using the generic type 'System.Collections.Generic.Comparer<T>' requires '1'
type arguments

Using refactor offering I changed this line to

int retValue = System.Collections.Comparer.Compare(xValue, yValue);

but now got error

An object reference is required for the non-static field, method, or
property 'System.Collections.Comparer.Compare(object, object)'

So I finally changed method to:

int IComparer<T>.Compare(T x, T y) {
object xValue = m_PropDesc.GetValue(x);
object yValue = m_PropDesc.GetValue(y);
if (xValue == null && yValue == null)
 return 0;
if (xValue == null && yValue != null)
 return 1;
if (xValue != null && yValue == null)
 return -1;
return CompareValues(xValue, yValue, m_Direction);
}

is this best style and solution ?

Andrus.
Marc Gravell - 25 Mar 2008 21:21 GMT
Oops; add a "Default" in there...

Comparer.Default.Compare(xValue, yValue)

Marc

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.