Results 1 to 3 of 3

Thread: [.NET 2.0+] Custom Typed Collections and Data-binding Support

Threaded View

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    [.NET 2.0+] Custom Typed Collections and Data-binding Support

    C# version here.

    If we want to use a standard collection in .NET 2.0 or later we use a generic List. If we want to create our own strongly-typed collection we used to have to inherit CollectionBase. That was a pain because, in the days before generics, we used to have to define all the type-specific members ourselves. We would inherit members like Clear and RemoveAt from CollectionBase because they didn't depend on the type of the items. Members like Item and Add though, whose return type or parameter type(s) depends on the type of the items, were left up to us. Since the introduction of generics, life has become much easier. To create a strongly typed collection with all the standard functionality we simply inherit the generic Collection class and that's it:
    vb.net Code:
    1. Public Class ThingCollection
    2.     Inherits System.Collections.ObjectModel.Collection(Of Thing)
    3. End Class
    All the standard functionality is inherited from the base class so we don't need to add any members of our own. That said, if we want to provide custom functionality then we can add our own members. The generic Collection class provides methods that you can override to process items as they are added and removed from the collection. One common use for those members is custom validation, e.g.
    vb.net Code:
    1. Public Class ThingCollection
    2.     Inherits System.Collections.ObjectModel.Collection(Of Thing)
    3.  
    4.     Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Thing)
    5.         Dim duplicateID As Boolean = False
    6.  
    7.         For Each existingItem As Thing In Me.Items
    8.             If item.ID = existingItem.ID Then
    9.                 duplicateID = True
    10.  
    11.                 Exit For
    12.             End If
    13.         Next
    14.  
    15.         If duplicateID Then
    16.             'Don't add an item with a duplicate ID.
    17.             Throw New ArgumentException("An item with the specified ID already exists")
    18.         Else
    19.             'Allow the item to be added.
    20.             MyBase.InsertItem(index, item)
    21.         End If
    22.     End Sub
    23.  
    24.     Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As Thing)
    25.         Dim duplicateID As Boolean = False
    26.  
    27.         For existingIndex As Integer = 0 To Me.Count - 1
    28.             'Ignore the existing item at the index being set because it is being replaced.
    29.             If existingIndex <> index AndAlso _
    30.                item.ID = Me.Items(existingIndex).ID Then
    31.                 duplicateID = True
    32.  
    33.                 Exit For
    34.             End If
    35.         Next
    36.  
    37.         If duplicateID Then
    38.             'Don't add an item with a duplicate ID.
    39.             Throw New ArgumentException("An item with the specified ID already exists")
    40.         Else
    41.             'Allow the item to be added.
    42.             MyBase.SetItem(index, item)
    43.         End If
    44.     End Sub
    45.  
    46. End Class
    So, that's nice and easy. Now, with regards to data-binding, you can quite easily bind either a generic List or your own strongly-typed collection to controls in your UI and the data they contain will be displayed. That's because they all implement the IList interface, which is all data-binding requires. The problem is, if you intend to make changes to your collection in code, like adding or removing items or editing properties of the items, then you'll be disappointed if you expect to see those changes reflected in the UI.

    That's because the bound control is not implicitly aware of any changes taking place in the data source. It must be explicitly notified of changes in order to know that it should update its display. In order to provide such notification your data source must implement the IBindingList interface, which neither the generic List nor generic Collection class does. The easy way out of this is to bind your data to a BindingSource and then bind that to the control(s). You can then call ResetCurrentItem, ResetItem or RestBindings on the BindingSource to raise its ListChanged event and thereby notify the bound control to update.

    That's all well and good, but what if the changes to the collection are occurring in code that can't see the BindingSource, like a business logic layer? In that case you need your collection to implement IBindingList itself. You could do that from scratch but you don't actually need to. Instead of deriving your collection directly from the generic Collection class, you can instead inherit the generic BindingList class, which itself inherits Collection and adds an IBindingList implementation. Again, you don't have to add any code of your own if you only want the standard functionality:
    vb.net Code:
    1. Public Class ThingCollection
    2.     Inherits System.ComponentModel.BindingList(Of Thing)
    3. End Class
    Now your collection will raise its ListChanged event automatically whenever you add, insert, set or remove an item or clear the list. Any bound controls will automatically update as a result.

    Now, that's fine for making changes to the list but what about if you make changes to items that are already in the list? Bound controls will not update automatically because the BindingList doesn't raise a ListChanged event automatically because it doesn't inherently know when an item changes. This is where you need to do a little bit of work.

    What needs to happen is that your item class needs to raise an event when a property value changes, e.g.
    vb.net Code:
    1. Public Class Thing
    2.  
    3.     Private _name As String
    4.  
    5.     Public Property Name() As String
    6.         Get
    7.             Return Me._name
    8.         End Get
    9.         Set(ByVal value As String)
    10.             If Me._name <> value Then
    11.                 Me._name = value
    12.                 Me.OnNameChanged(EventArgs.Empty)
    13.             End If
    14.         End Set
    15.     End Property
    16.  
    17.     Public Event NameChanged As EventHandler
    18.  
    19.     Protected Overridable Sub OnNameChanged(ByVal e As EventArgs)
    20.         RaiseEvent NameChanged(Me, e)
    21.     End Sub
    22.  
    23. End Class
    Your typed collection can now handle that event and raise its own ListChanged event to notify any bound controls of the change:
    vb.net Code:
    1. Public Class ThingCollection
    2.     Inherits System.ComponentModel.BindingList(Of Thing)
    3.  
    4.     Private Sub HandleNameChanged(ByVal sender As Object, ByVal e As EventArgs)
    5.         Me.OnListChanged(New ListChangedEventArgs(ListChangedType.ItemChanged, _
    6.                                                   Me.Items.IndexOf(DirectCast(sender,  _
    7.                                                                               Thing))))
    8.     End Sub
    9.  
    10. End Class
    Note that you specify ItemChanged as the change type.

    Now, that method isn't going to handle any events on its own. We need to attach it to the NameChanged event of each item as it gets added to the list. We also need to make sure we detach when an item gets removed from the list. For that we need to override methods inherited from the Collection class, much as we did for the validation earlier:
    vb.net Code:
    1. Public Class ThingCollection
    2.     Inherits System.ComponentModel.BindingList(Of Thing)
    3.  
    4.     Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Thing)
    5.         MyBase.InsertItem(index, item)
    6.  
    7.         'Attach the event handler to the item being added.
    8.         AddHandler item.NameChanged, AddressOf HandleNameChanged
    9.     End Sub
    10.  
    11.     Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As Thing)
    12.         'Remove the event handler from the item being removed.
    13.         RemoveHandler Me.Items(index).NameChanged, AddressOf HandleNameChanged
    14.  
    15.         MyBase.SetItem(index, item)
    16.  
    17.         'Attach the event handler to the item being added.
    18.         AddHandler item.NameChanged, AddressOf HandleNameChanged
    19.     End Sub
    20.  
    21.     Protected Overrides Sub RemoveItem(ByVal index As Integer)
    22.         'Remove the event handler from the item being removed.
    23.         RemoveHandler Me.Items(index).NameChanged, AddressOf HandleNameChanged
    24.  
    25.         MyBase.RemoveItem(index)
    26.     End Sub
    27.  
    28.     Protected Overrides Sub ClearItems()
    29.         'Remove the event handler from all existing items.
    30.         For Each item As Thing In Me.Items
    31.             RemoveHandler item.NameChanged, AddressOf HandleNameChanged
    32.         Next
    33.  
    34.         MyBase.ClearItems()
    35.     End Sub
    36.  
    37.     Private Sub HandleNameChanged(ByVal sender As Object, ByVal e As EventArgs)
    38.         Me.OnListChanged(New ListChangedEventArgs(ListChangedType.ItemChanged, _
    39.                                                   Me.Items.IndexOf(DirectCast(sender,  _
    40.                                                                               Thing))))
    41.     End Sub
    42.  
    43. End Class
    Another useful feature of the IBindingList interface is simple sorting support, i.e. sorting by a single column/property. By adding such support to your typed collection you enable, for instance, a user to click a column header in a DataGridView bound to your collection and have the data sorted automatically. I'll look at that in the next installment. Stay tuned!
    Last edited by jmcilhinney; Nov 27th, 2008 at 07:19 PM.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width