DataGridViewにEntity/DTO入りのListを繋げてSort/Filterしたいので、ライブラリ探し&サンプル作成
S2Dao.NETを使ってて悩んだのはここ。
せっかくモデルクラスに閉じ込めて固くしたデータを、表示/編集のためだけにDataTableに展開するのは勿体ない。
.NETフレームワークには、ソート/フィルターするためのIBindingListViewインターフェースはあるが、これを実装したクラスはDataViewだけ。そしてDataViewはDataTable専用。標準フレームワークにDataObjectViewとか用意しといて欲しいぞ。
定石ライブラリはあるかと探したが見つからず。フル装備っぽいBindingListViewというクラスを提供するライブラリもある。が、ちょっと大きいか。
http://blw.sourceforge.net/
以下は最低限の機能を実装したサンプルのよう。
http://lemoncake.wordpress.com/2007/06/13/filtering-on-a-list-bindingsource/
これをVB.NET用に移植してみた。このままでは実用にはならない(BindingListから継承してるのでItems周りの制御を奪うのが大変。IBindingListを自分で実装すべきなんだろう)がサンプルとして。
Imports System Imports System.Collections.Generic Imports System.Collections Imports System.Text Imports System.ComponentModel Namespace Sample '====================================================================== 'データオブジェクトビューのサンプル(DataViewの一般Object版的ななにか) ' 'via. http://lemoncake.wordpress.com/2007/06/13/filtering-on-a-list-bindingsource/ ' Public Class DataObjectView(Of T) Inherits BindingList(Of T) Implements IBindingListView, IRaiseItemChangedEvents Private m_Sorted As Boolean = False Private m_Filtered As Boolean = False Private m_FilterString As String = Nothing Private m_SortDirection As ListSortDirection = ListSortDirection.Ascending Private m_SortProperty As PropertyDescriptor = Nothing Private m_SortDescriptions As New ListSortDescriptionCollection() Private m_OriginalCollection As New List(Of T)() Public Sub New() MyBase.New() End Sub Public Sub New(ByRef aList As IList(Of T)) MyBase.New(aList) End Sub Protected Overrides ReadOnly Property SupportsSearchingCore As Boolean Get Return True End Get End Property Protected Overrides Function FindCore(ByVal prop As System.ComponentModel.PropertyDescriptor, ByVal key As Object) As Integer For i As Integer = 0 To Count - 1 Dim item As T = Me(i) If prop.GetValue(item).Equals(key) Then Return i End If Next Return -1 End Function Protected Overrides ReadOnly Property SupportsSortingCore As Boolean Get Return True End Get End Property Protected Overrides ReadOnly Property IsSortedCore As Boolean Get Return m_Sorted End Get End Property Protected Overrides ReadOnly Property SortDirectionCore As ListSortDirection Get Return m_SortDirection End Get End Property Protected Overrides ReadOnly Property SortPropertyCore As PropertyDescriptor Get Return m_SortProperty End Get End Property Protected Overrides Sub ApplySortCore(ByVal prop As System.ComponentModel.PropertyDescriptor, ByVal direction As System.ComponentModel.ListSortDirection) m_SortDirection = direction m_SortProperty = prop Dim comparer As SortComparer(Of T) = New SortComparer(Of T)(prop, direction) ApplySortInternal(comparer) End Sub Private Sub ApplySortInternal(ByVal comparer As SortComparer(Of T)) If m_OriginalCollection.Count = 0 Then m_OriginalCollection.AddRange(Me) End If Dim listRef As List(Of T) = Me.Items If listRef Is Nothing Then Exit Sub listRef.Sort(comparer) m_Sorted = True OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1)) End Sub Protected Overrides Sub RemoveSortCore() If Not m_Sorted Then Return Clear() For Each Item As T In m_OriginalCollection Add(Item) Next m_OriginalCollection.Clear() m_SortProperty = Nothing m_SortDescriptions = Nothing m_Sorted = False End Sub Public Sub ApplySort(ByVal sorts As System.ComponentModel.ListSortDescriptionCollection) Implements System.ComponentModel.IBindingListView.ApplySort m_SortProperty = Nothing m_SortDescriptions = sorts Dim comparer As SortComparer(Of T) = New SortComparer(Of T)(sorts) ApplySortInternal(comparer) End Sub Public Property Filter As String Implements System.ComponentModel.IBindingListView.Filter Get Return m_FilterString End Get Set(ByVal value As String) m_FilterString = value m_Filtered = True UpdateFilter() End Set End Property Public Sub RemoveFilter() Implements System.ComponentModel.IBindingListView.RemoveFilter If Not m_Filtered Then Return m_FilterString = Nothing m_Filtered = False m_Sorted = False m_SortDescriptions = Nothing m_SortProperty = Nothing Clear() For Each item As T In m_OriginalCollection Add(item) Next m_OriginalCollection.Clear() End Sub Public ReadOnly Property SortDescriptions As System.ComponentModel.ListSortDescriptionCollection Implements System.ComponentModel.IBindingListView.SortDescriptions Get Return m_SortDescriptions End Get End Property Public ReadOnly Property SupportsAdvancedSorting As Boolean Implements System.ComponentModel.IBindingListView.SupportsAdvancedSorting Get Return True End Get End Property Public ReadOnly Property SupportsFiltering As Boolean Implements System.ComponentModel.IBindingListView.SupportsFiltering Get Return True End Get End Property Protected Overridable Sub UpdateFilter() If String.IsNullOrEmpty(m_FilterString) Then RemoveFilter() Exit Sub End If Dim equalsPos As Integer = m_FilterString.IndexOf("=") ' Get property name Dim propName As String = m_FilterString.Substring(0, equalsPos).Trim() ' Get filter criteria Dim criteria As String = m_FilterString.Substring(equalsPos + 1, m_FilterString.Length - equalsPos - 1).Trim() ' Strip leading and trailing quotes criteria = criteria.Substring(1, criteria.Length - 2) ' Get a property descriptor for the filter property Dim propDesc As PropertyDescriptor = TypeDescriptor.GetProperties(GetType(T))(propName) If m_OriginalCollection.Count = 0 Then m_OriginalCollection.AddRange(Me) End If Clear() ' ' This implementation simulates a LIKE '%term%' and will ' find your term within the field ' For Each item As T In m_OriginalCollection Dim value = propDesc.GetValue(item) ' strip newline characters as it was causing issues with contains Dim sval As String = value.ToString().Replace(vbCr, "").Replace(vbLf, "") If sval.ToString().Contains(criteria) Then Add(item) End If Next End Sub Public Overloads ReadOnly Property AllowNew As Boolean Get Return CheckReadOnly() End Get End Property Public Overloads ReadOnly Property AllowRemove As Boolean Get Return CheckReadOnly() End Get End Property Private Function CheckReadOnly() As Boolean If m_Sorted Or m_Filtered Then Return False Else Return True End If End Function Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T) For Each propDesc As PropertyDescriptor In TypeDescriptor.GetProperties(item) If propDesc.SupportsChangeEvents Then propDesc.AddValueChanged(item, AddressOf OnItemChanged) End If Next MyBase.InsertItem(index, item) End Sub Protected Overrides Sub RemoveItem(ByVal index As Integer) Dim item As T = Items(index) Dim propDescs As PropertyDescriptorCollection = TypeDescriptor.GetProperties(item) For Each propDesc As PropertyDescriptor In propDescs If propDesc.SupportsChangeEvents Then propDesc.RemoveValueChanged(item, AddressOf OnItemChanged) End If Next MyBase.RemoveItem(index) End Sub Private Sub OnItemChanged(ByVal sender As Object, ByVal args As EventArgs) Dim index As Integer = Items.IndexOf(DirectCast(sender, T)) OnListChanged(New ListChangedEventArgs( ListChangedType.ItemChanged, index)) End Sub ReadOnly Property RaisesItemChangedEvents As Boolean Get Return True End Get End Property End Class '====================================================================== 'ソート用比較処理クラス Public Class SortComparer(Of T) Implements IComparer(Of T) Private m_SortCollection As ListSortDescriptionCollection = Nothing Private m_PropDesc As PropertyDescriptor = Nothing Private m_Direction As ListSortDirection = ListSortDirection.Ascending Public Sub New(ByVal propDesc As PropertyDescriptor, ByVal direction As ListSortDirection) m_PropDesc = propDesc m_Direction = direction End Sub Public Sub New(ByVal sortCollection As ListSortDescriptionCollection) m_SortCollection = sortCollection End Sub Public Function Compare(ByVal x As T, ByVal y As T) As Integer Implements System.Collections.Generic.IComparer(Of T).Compare If m_PropDesc IsNot Nothing Then Dim xValue = m_PropDesc.GetValue(x) Dim yValue = m_PropDesc.GetValue(y) Return CompareValues(xValue, yValue, m_Direction) ElseIf m_SortCollection IsNot Nothing AndAlso m_SortCollection.Count > 0 Then Return RecursiveCompareInternal(x, y, 0) Else Return 0 End If End Function Private Function CompareValues(ByVal xValue As Object, ByVal yValue As Object, ByVal direction As ListSortDirection) Dim retValue As Integer = 0 If TypeOf xValue Is IComparable Then retValue = DirectCast(xValue, IComparable).CompareTo(yValue) ElseIf TypeOf yValue Is IComparable Then retValue = DirectCast(yValue, IComparable).CompareTo(xValue) ElseIf Not xValue.Equals(yValue) Then retValue = xValue.ToString().CompareTo(yValue.ToString()) End If If direction = ListSortDirection.Ascending Then Return retValue Else Return retValue * -1 End If End Function Private Function RecursiveCompareInternal(ByVal x As T, ByVal y As T, ByVal index As Integer) As Integer If index >= m_SortCollection.Count Then Return 0 Dim listSortDesc As ListSortDescription = m_SortCollection(index) Dim xValue = listSortDesc.PropertyDescriptor.GetValue(x) Dim yValue = listSortDesc.PropertyDescriptor.GetValue(y) Dim retValue As Integer = CompareValues(xValue, yValue, listSortDesc.SortDirection) If retValue = 0 Then index += 1 Return RecursiveCompareInternal(x, y, index) Else Return retValue End If End Function End Class End Namespace