I am using a List of (T) which is bound to a BindingSource component, and this component is bound to a grid, however when I have come to try and sort the list programmatically, I get the following error:
DataGridView control cannot be sorted if it is bound to an IBindingList that does not support sorting.
The thing is I really, really need the grid to be sortable programmatically by any means necessary, if you have any suggestions I would appreciate hearing them.
Um, you say that you want to sort the grid programmatically but what you actually mean is that you want it to sort itself automatically. You absolutely can sort it programmatically, but that means that you would write the code to sort the items yourself, not just expect the grid to do it for you.
The thing is, the only way that a DataGridView will sort automatically for you is if its data source implements the IBindingList interface and supports sorting. The generic List class does NOT implement the IBindingList interface so it will not sort automatically. If you want the grid to be able to sort automatically then you'll need to create your own class that does implement IBindingList and dies support sorting. You can use the generic BindingList class as a base for such a class. I suggest that you read the documentation for the IBindingList interface and the BindingList class.
Note that the DataView class DOES implement the IBindingList interface and DOES support sorting. That's why a DataGridView bound to a DataTable will sort automatically. The DataView class actually implements the IBindingListView interface, which is more advanced that IBindingList. IBindingList provides for simple sorting, i.e. by a single column. IBindingListView provides for complex sorting, i.e. by multiple columns, and filtering.
One thing I forgot to mention is that my list is bound to a BindingSource componenet, and this is what populates the datagridview with data. I have had a good read on MSDN about IBindingList.
This is what i have managed to put together
Code:
Public Class List(Of T) : Inherits BindingList(Of User)
Implements IBindingList
End Class
Here's what I use to created the BindingList (Note I changed from List of (t) to bindinglist from my previous post)
Code:
Dim Users As New BindingList(Of User)
The thing is if the first block of code is correct, do I simple paste the class in with my form class, or inherit from it etc?
Thanks for your help once again
Learning C♯
Data Binding & Bound Controls - Objects and wizards will never be as intelligent as you, do it yourself! (Unless your Pro)
First up, the BindingList class already implements the IBindingList interface so, if you inherit BindingList, there's no need or point to explicitly implementing IBindingList.
Secondly, the BindingList class is generic to begin with. If you want your class to be generic too then you do NOT fix the generic type of the BindingList when you inherit it:
vb.net Code:
Public Class List(Of T)
Inherits BindingList(Of T)
if you want your class to be strongly-typed then you DO fix the generic type of the BindingList when you inherit it, but you do NOT make your class generic:
vb.net Code:
Public Class UserList
Inherits BindingList(Of User)
I think you'll need to go with the second option if you expect to implement your own sorting routine.
That brings me to the last point. Now you have to override the appropriate members of the BindingList class and implement your own sorting routine.
Okay well I'm going to use the sorting routines already available because I don't required anything special, and its much easier for me.
The thing is, I'm not really sure what to do with the List of (T) class, I mean do I simply insert:
Code:
Public Class List(Of T)
Inherits BindingList(Of T)
End Class
Within my form or a module because its got the exact same name as the default List (Of T) class, therefore the default class will just inherit BindingList of (T)'s properties and methods etc. I'm probably totally wrong, its just that I only create a list by declaring it and then pass my User class through it which just has a few properties and a construct.
If it helps I can copy my code, as I'm confusing myself here
Thanks
Last edited by Bopo; Nov 29th, 2008 at 07:05 PM.
Reason: noticed typo's
Learning C♯
Data Binding & Bound Controls - Objects and wizards will never be as intelligent as you, do it yourself! (Unless your Pro)
No, the System.Collections.Generic.List(Of T) class will not inherit the System.ComponentModel.BindingList(Of T) class. You can't change the base class of an existing class. You're declaring a new type: the WindowsApplication1.List(Of T) class, or whatever the default namespace is for your project.
That said, it's probably better to use a name other than "List" to avoid confusion. Follow the Custom Collection link in my signature for an explanation. I haven't got as far as sorting yet but what you'll need to do is:
1. Define your own class that implements the IComparer interface by taking a property name and comparing on the property with that name.
2. Override the SupportsSortingCore property to return True, to indicate that sorting is supported.
3. Override the ApplySortCore method to sort the items using an instance of your IComparer class.
Public Class RowComparer
Implements System.Collections.IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
End Function
End Class
I actually found an example of sorting an unbound datagridview and tried it out just for the sake of it, obviously it threw an exception:
Code:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
dgvuserdetails.Sort(New RowComparer(SortOrder.Ascending))
End Sub
Public Class RowComparer
Implements System.Collections.IComparer
Private Shared SortOrderModifier As Integer = 1
Public Sub New(ByVal sortOrder_1 As SortOrder)
If sortOrder_1 = SortOrder.Ascending Then
SortOrderModifier = 1
End If
End Sub
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim DGVRow1 As DataGridViewRow = DirectCast(x, DataGridViewRow)
Dim DGVRow2 As DataGridViewRow = DirectCast(y, DataGridViewRow)
Dim CompareResults As Integer = System.String.Compare(DGVRow1.Cells(0).Value.ToString(), DGVRow2.Cells(0).Value.ToString())
End Function
End Class
Last edited by Bopo; Nov 30th, 2008 at 11:05 AM.
Learning C♯
Data Binding & Bound Controls - Objects and wizards will never be as intelligent as you, do it yourself! (Unless your Pro)
You would be better served implementing the generic IComparer interface. It will need to accept a column/property to compare by and a sort direction. These values are set by the collection when it creates an instance. Those values are then used in the Compare method.
Okay I am now using the generic ICompare interface, however I can't find an example of how I would write code to tell the interface to accept the column/property to compare by, I known your a busy guy, but I would really appreciate an example when you have some free time
Code:
Imports System.Collections.Generic
Public Class RowComparer
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
End Function
Thanks
Learning C♯
Data Binding & Bound Controls - Objects and wizards will never be as intelligent as you, do it yourself! (Unless your Pro)
You don't need an example. You already know how to get data into an object because you do it all the time. You set a property or call a method and pass an argument. This is no different. The most logical choice is to add a constructor with a parameter for the property to sort by. That way you can't create an instance without providing that data.
Attached is the code I intend to post in the CodeBank. It's not quite complete and it's undocumented but it will work as is and it shows you how to implement the IComparer interface and how to use that class to sort the collection.
Sorry about the late reply, been away for a few days, anyway that class looks amazing, however no matter how hard I try, I just can't get it to work for me, I been using trial and error for the past hour or so and Im not getting anywhere, its not actually throwing an error either .
Anyway to make sure my coding was not interfering, I re-made the data binding and sorting part in another project, and once again no error occured but nothing happened, below is the code which is identical to my original project
Form:
Code:
Imports System.ComponentModel
Public Class Form1
Dim Users As New BindingList(Of User)
Dim People As New PersonCollection
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Users.Add(New User(TextBox1.Text, TextBox2.Text, TextBox3.Text))
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
DataGridView1.DataSource = BindingSource1
BindingSource1.DataSource = Me.Users
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
People.Sort = "FirstName"
End Sub
End Class
User Class:
Code:
Imports System.ComponentModel
Public Class User
Private _firstName As String
Private _lastName As String
Private _age As Integer
Public Property FirstName()
Get
Return _firstName
End Get
Set(ByVal value)
_firstName = value
End Set
End Property
Public Property LastName()
Get
Return _lastName
End Get
Set(ByVal value)
_lastName = value
End Set
End Property
Public Property Age()
Get
Return _age
End Get
Set(ByVal value)
_age = value
End Set
End Property
Public Sub New(ByVal FName As String, ByVal LName As String, ByVal AG As Integer)
_firstName = FName
_lastName = LName
_age = AG
End Sub
End Class
JMC's Class:
Code:
Imports System.ComponentModel
Public Class PersonCollection
Inherits System.ComponentModel.BindingList(Of Person)
#Region " Types "
Private Class PersonComparer
Implements System.Collections.Generic.IComparer(Of Person)
Private prop As PropertyDescriptor
Private direction As ListSortDirection
Public Function Compare(ByVal x As Person, _
ByVal y As Person) As Integer Implements IComparer(Of Person).Compare
Dim result As Integer = DirectCast(Me.prop.GetValue(x), IComparable).CompareTo(Me.prop.GetValue(y))
If Me.direction = ListSortDirection.Descending Then
result = -result
End If
Return result
End Function
Public Sub New(ByVal prop As PropertyDescriptor, ByVal direction As ListSortDirection)
Me.prop = prop
Me.direction = direction
End Sub
End Class
#End Region 'Types
#Region " Variables "
Private _sort As String
Private _sortProperty As PropertyDescriptor
Private _sortDirection As ListSortDirection
#End Region 'Variables
#Region " Properties "
Public Property Sort() As String
Get
Return Me._sort
End Get
Set(ByVal value As String)
Dim prop As PropertyDescriptor = Nothing
Dim direction As ListSortDirection
Me.ParseSortClause(value, prop, direction)
Me._sort = value
If prop Is Nothing Then
Me.RemoveSortCore()
Else
Me.ApplySortCore(prop, direction)
End If
End Set
End Property
Protected Overrides ReadOnly Property SortDirectionCore() As System.ComponentModel.ListSortDirection
Get
Return Me._sortDirection
End Get
End Property
Protected Overrides ReadOnly Property SortPropertyCore() As System.ComponentModel.PropertyDescriptor
Get
Return Me._sortProperty
End Get
End Property
Protected Overrides ReadOnly Property SupportsSortingCore() As Boolean
Get
Return True
End Get
End Property
#End Region 'Properties
#Region " Methods "
Protected Overrides Sub ApplySortCore(ByVal prop As PropertyDescriptor, ByVal direction As ListSortDirection)
Me._sortProperty = prop
Me._sortDirection = direction
Dim upperBound As Integer = Me.Items.Count - 1
Dim items(upperBound) As Person
Me.Items.CopyTo(items, 0)
Array.Sort(items, _
New PersonComparer(prop, _
direction))
For index As Integer = 0 To upperBound
Me.Items(index) = items(index)
Next
Me.OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0))
End Sub
Protected Overrides Sub RemoveSortCore()
Me._sortProperty = Nothing
Me._sortDirection = Nothing
End Sub
Private Sub ParseSortClause(ByVal clause As String, _
ByRef prop As PropertyDescriptor, _
ByRef direction As ListSortDirection)
If clause IsNot Nothing AndAlso clause.Trim() <> String.Empty Then
Dim parts As String() = clause.Split(" "c)
If parts.Length > 2 Then
Throw New ArgumentException("Invalid sort clause")
End If
For index As Integer = 0 To parts.GetUpperBound(0)
parts(index) = parts(index).Trim()
Next
prop = TypeDescriptor.GetProperties(GetType(Person))(parts(0))
If prop Is Nothing Then
Throw New ArgumentException("Invalid property name")
End If
If parts.Length = 1 OrElse _
parts(1) = String.Empty OrElse _
String.Compare(parts(1), "ASC", True) = 0 Then
direction = ListSortDirection.Ascending
ElseIf String.Compare(parts(1), "DESC", True) = 0 Then
direction = ListSortDirection.Descending
Else
Throw New ArgumentException("Invalid sort direction")
End If
End If
End Sub
#End Region 'Methods
End Class
Learning C♯
Data Binding & Bound Controls - Objects and wizards will never be as intelligent as you, do it yourself! (Unless your Pro)
Um, you are creating a PersonCollection but you aren't binding it to your grid. You're binding a plain old BindingList, which doesn't support sorting, to the grid. Why 2 collections?
I'm on a mobile phone at the moment and it's not ideal for jumping between threads and reading long code listings. As a result I may be missing something but it seems that you've missed the point of what I was trying to say.
My code is an EXAMPLE of a CUSTOM collection. It allows you to sort a collection of Person objects, but you don't want to sort Person objects. You want to sort User objects. You're supposed to define your own UserCollection using the pattern I've provided.
UnFreakingBelievable... I can't tell you how much I learned from going through your code about how many different things!!!!!
It's amazing that it's more than 4 years old...
Geez, you are the same guy who helped me get started last month when I couldn't get an MSDN example to run. Thank you, again, Mr. jmcilhinney.. Thank you, very, very much.