Guest Author: Ed Marquez
This is my base for Type Safe Collections. It uses the CollectionBase but then adds the use of object keys internally, a CountChange event, and an easy system to make sure a DataGrid bound to it updates itself on changes. To use the class just inherit from it and add Shadowed version of the methods that take an object parameter, replacing the object type with the type safe object and then internally just call the NON type safe version of the method to carry out the work. All the Protected methods are the ones that should be replaced.
Public MustInherit Class KeyedCollectionBase
'''''''''''''''''''''''''''''''''''''''''''''''''''
' Author: Ed Marquez
' Description: This is my base for type safe collections. It uses the collectionbase but then adds
' the use of object keys internally, a countchange event, and an easy system to make
' sure a datagrid bound to it updates itself on changes. To use the class just inherit
' from it and add Shadowed version of the methods that take an object parameter, replacing
' the object type with the type safe object and then internally just call the NON type safe
' version of the method to carry out the work. All the Protected methods are the ones that
' should be replaced.
'''''''''''''''''''''''''''''''''''''''''''''''''''
Inherits System.Collections.CollectionBase
Protected keys As New SortedList()
Protected keymember As String
Private boundgrid As DataGrid
Public Event CountChange(ByVal sender As Object, ByVal e As CountEventArgs)
Protected Function Add(ByVal Items As KeyedCollectionBase) As Integer()
Dim al As New ArrayList()
Dim itm As Object
For Each itm In Items
Dim key As Object = itm.GetType.GetProperty(keymember).GetValue(itm, Nothing)
keys.Add(key, MyBase.List.Add(itm))
al.Add(keys(key))
Next
Return al.ToArray(GetType(Integer))
End Function
Protected Function Add(ByVal Items() As Object) As Integer()
Dim al As New ArrayList()
Dim itm As Object
For Each itm In Items
Dim key As Object = itm.GetType.GetProperty(keymember).GetValue(itm, Nothing)
keys.Add(key, MyBase.List.Add(itm))
al.Add(keys(key))
Next
Return al.ToArray(GetType(Integer))
End Function
Protected Function Add(ByVal Item As Object) As Integer
Dim key As Object = Item.GetType.GetProperty(keymember).GetValue(Item, Nothing)
keys.Add(key, MyBase.List.Add(Item))
Return keys(key)
End Function
Protected Function Add(ByVal Item As Object, ByVal Key As Object) As Integer
keys.Add(Key, MyBase.List.Add(Item))
Return keys(Key)
End Function
Default Protected ReadOnly Property Item(ByVal Key As String) As Object
Get
Return MyBase.List.Item(keys(Key))
End Get
End Property
Default Protected ReadOnly Property Item(ByVal Index As Integer) As Object
Get
Return MyBase.List.Item(Index)
End Get
End Property
Protected Sub Remove(ByVal Item As Object)
Dim Index As Integer = keys.IndexOfValue(Me.IndexOf(Item))
keys.RemoveAt(keys.IndexOfValue(Me.IndexOf(Item)))
MyBase.List.Remove(Item)
DecreaseIndexes(Index)
End Sub
Public Shadows Sub RemoveAt(ByVal Key As Object)
Dim Index As Integer = keys(Key)
keys.Remove(Key)
MyBase.List.RemoveAt(Index)
DecreaseIndexes(Index)
End Sub
Public Shadows Sub RemoveAt(ByVal Index As Integer)
keys.RemoveAt(keys.IndexOfValue(Index))
MyBase.List.RemoveAt(Index)
DecreaseIndexes(Index)
End Sub
Public Function IndexOf(ByVal Key As Object) As Integer
Return MyBase.List.IndexOf(MyBase.List.Item(keys(Key)))
End Function
Public Function IndexOf(ByVal Item As Integer) As Integer
MyBase.List.IndexOf(Item)
End Function
Protected Sub Insert(ByVal Index As Integer, ByVal Item As Object)
Dim key As Object = Item.GetType.GetProperty(keymember).GetValue(Item, Nothing)
IncreaseIndexes(Index)
keys.Add(key, Index)
MyBase.List.Insert(Index, Item)
End Sub
Protected Sub Insert(ByVal Index As Integer, ByVal Item As Object, ByVal Key As Object)
IncreaseIndexes(Index)
keys.Add(Key, Index)
MyBase.List.Insert(Index, Item)
End Sub
Protected Function Contains(ByVal Item As Object) As Boolean
Return MyBase.List.Contains(Item)
End Function
Public Sub New(ByVal KeyMember As String)
MyBase.New()
'this allows you to pass in the default property of the object to use
'as a key when no key is passed in. To not use this feature then pass in
'Nothing
Me.keymember = KeyMember
End Sub
Protected Overrides Sub OnClearComplete()
'used to refresh a datagrid
MyBase.OnClearComplete()
If Not boundgrid Is Nothing Then RefreshGrid()
'raise count change event
RaiseEvent CountChange(Me, New CountEventArgs(CountEventArgs.ChangeTypes.Cleared))
End Sub
Protected Overrides Sub OnInsertComplete(ByVal index As Integer, ByVal value As Object)
'see OnClearComplete for comments
MyBase.OnInsertComplete(index, value)
If Not boundgrid Is Nothing Then RefreshGrid()
RaiseEvent CountChange(Me, New CountEventArgs(CountEventArgs.ChangeTypes.Increased, value))
End Sub
Protected Overrides Sub OnRemoveComplete(ByVal index As Integer, ByVal value As Object)
'see OnClearComplete for comments
MyBase.OnRemoveComplete(index, value)
If Not boundgrid Is Nothing Then RefreshGrid()
RaiseEvent CountChange(Me, New CountEventArgs(CountEventArgs.ChangeTypes.Decreased, value))
End Sub
Protected Overrides Sub OnSetComplete(ByVal index As Integer, ByVal oldValue As Object, ByVal newValue As Object)
'see OnClearComplete for comments
If Not boundgrid Is Nothing Then RefreshGrid()
End Sub
Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer)
MyBase.InnerList.CopyTo(array, index)
End Sub
Public Sub BoundDataGrid(ByVal dg As DataGrid)
'set to nothing if the object is no longer bound
'see refreshgrid for comments
boundgrid = dg
End Sub
'supportive
Private Sub IncreaseIndexes(ByVal start As Integer)
'increment values of all indexes after start
'the internal list updates all the indexes on a removeat,remove or insert
'but in the sorted list the the index is stored as a value and thus
'doesn't get updated automatically this fixes that
'call BEFORE the insert
Dim values As IList = keys.GetValueList
Dim cnt As Integer
Dim cntMax As Integer = values.Count - 1
For cnt = cntMax To 0 Step -1
If values(cnt) >= start Then
keys.SetByIndex(keys.IndexOfValue(values(cnt)), values(cnt) + 1)
End If
Next
End Sub
Private Sub DecreaseIndexes(ByVal start As Integer)
'decrement values of all keys after key
'the internal list updates all the indexes on a removeat,remove or insert
'but in the sorted list the the index is stored as a value and thus
'doesn't get updated automatically this fixes that
'call AFTER the index has been removed
Dim values As IList = keys.GetValueList
Dim cnt As Integer
Dim cntMax As Integer = values.Count - 1
For cnt = cntMax To 0 Step -1
If values(cnt) >= start Then
keys.SetByIndex(keys.IndexOfValue(values(cnt)), values(cnt) - 1)
End If
Next
End Sub
Private Sub RefreshGrid()
'a datagrid doesn't refresh or gives errors when bound to a collection or array
'when items are either added or removed but if you bind to nothing then to the source again
'it solves the problem
boundgrid.DataSource = Nothing
boundgrid.DataSource = Me
End Sub
'Common CountEventArgs
Public Class CountEventArgs
'Purpose: Common count change change event arguments for collections.
Inherits EventArgs
Public Enum ChangeTypes
[Increased]
[Decreased]
[Cleared]
End Enum
Private _ChangeType As ChangeTypes
Private _Item As Object
Public Property ChangeType() As ChangeTypes
Get
Return _ChangeType
End Get
Set(ByVal Value As ChangeTypes)
_ChangeType = Value
End Set
End Property
Public Property Item() As Object
Get
Return _Item
End Get
Set(ByVal Value As Object)
_Item = Value
End Set
End Property
Public Sub New(ByVal changetype As ChangeTypes)
Me.New(changetype, Nothing)
End Sub
Public Sub New(ByVal changetype As ChangeTypes, ByVal item As Object)
MyBase.new()
_ChangeType = changetype
_Item = item
End Sub
End Class
End Class
Example of Type Safe Collection
'object for example of type safe collection
Public Class TestObject
Private _Name As String
Private _Address As String
Private _Phone As String
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal Value As String)
_Name = Value
End Set
End Property
Public Property Address() As String
Get
Return _Address
End Get
Set(ByVal Value As String)
_Address = Value
End Set
End Property
Public Property Phone() As String
Get
Return _Phone
End Get
Set(ByVal Value As String)
_Phone = Value
End Set
End Property
Public Sub New()
MyBase.new()
End Sub
Public Sub New(ByVal name As String, ByVal address As String, ByVal phone As String)
_Name = name
_Address = address
_Phone = phone
End Sub
End Class
'example of use collection
Public Class TestCollection
Inherits KeyedCollectionBase
Public Shadows Function Add(ByVal Item As TestObject) As Integer
Return MyBase.Add(Item)
End Function
Public Shadows Function Add(ByVal Item As TestObject, ByVal Key As Object) As Integer
Return MyBase.Add(Item, Key)
End Function
Default Public Shadows ReadOnly Property Item(ByVal Key As String) As TestObject
Get
Return MyBase.Item(Key)
End Get
End Property
Default Public Shadows ReadOnly Property Item(ByVal Index As Integer) As TestObject
Get
Return MyBase.Item(Index)
End Get
End Property
Public Shadows Sub Remove(ByVal Item As TestObject)
MyBase.Remove(Item)
End Sub
Public Shadows Sub Insert(ByVal Key As Object, ByVal Item As TestObject)
MyBase.Insert(Key, Item)
End Sub
Public Shadows Sub Insert(ByVal Index As Integer, ByVal Item As TestObject)
MyBase.Insert(Index, Item)
End Sub
Public Shadows Function Contains(ByVal Item As TestObject) As Boolean
Return MyBase.Contains(Item)
End Function
Public Sub New()
MyBase.new("Name")
End Sub
Public Sub New(ByVal KeyMember As String)
MyBase.New(KeyMember)
End Sub
End Class