.NET Forum / Languages / C# / March 2008
Sorting Bindinglist based DataGridView
|
|
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
Free MagazinesGet 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 ...
|
|
|