DataGridView binding with abstract base classes by ThinqLinq

DataGridView binding with abstract base classes

I recently ran into one of the most frustrating issues to face someone debugging a released application: a bug which only appears intermittently. In the instance, I have a collection of business objects that are being bound to a DataGridView. Periodically, the grid would throw repeated TargetInvocationExceptions when being bound.

In my case, the collection contained both objects directly implementing a base class and other objects inheriting from the base class and extending upon it. I found that if the first item in the list was in instance of the base class, the grid would render correctly. However, if the first item was a special instance type inheriting from this base class, the DataGridView would bomb out. I suspected the framework infers the type of the first object and tries to cast any subsequent objects to this specific type. It does not fall back to a base class type.

I confirmed my suspicions by testing a collection of objects inheriting from an abstract base class. In the code below, I’m using an abstract base class of Animal. From this, I have two implementing classes: Dog and Cat.

Public MustInherit Class Animal

Private _numFeet As Integer

Private _name As String

Public Property numFeet() As Integer

Get

Return _numFeet

End Get

Set(ByVal value As Integer)

_numFeet = value

End Set

End Property

Public Property Name() As String

Get

Return _name

End Get

Set(ByVal value As String)

_name = value

End Set

End Property

Public Overridable ReadOnly Property PetType() As String

Get

Return "Unknown"

End Get

End Property

Public Sub New()

End Sub

Public Sub New(ByVal petName As String, ByVal numFeet As Integer)

_name = petName

_numFeet = numFeet

End Sub

End Class

Public Class Dog

Inherits Animal

Public Sub New(ByVal petName As String, ByVal numFeet As Integer)

MyBase.New(petName, numFeet)

End Sub

Public Overrides ReadOnly Property PetType() As String

Get

Return "Dog"

End Get

End Property

End Class

Public Class Cat

Inherits Animal

Public Sub New(ByVal petName As String, ByVal numFeet As Integer)

MyBase.New(petName, numFeet)

End Sub

Public Overrides ReadOnly Property PetType() As String

Get

Return "Cat"

End Get

End Property

End Class

I then created a collection of “animals” as follows:

Public Class PetList

Inherits CollectionBase

Implements System.ComponentModel.IBindingList

Public Sub Add(ByVal pet As Animal)

List.Add(pet)

End Sub

#Region " IBindingList Implementation "

‘Implementation omitted for brevity. This must be implemented for binding purposes

#End Region

End Class

The form implementation is rather simple. Add a DataGridView to the form and the following code on the Form Load event:

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Dim animalList As New PetList

animalList.Add(New Cat("Fluffy", 4))

animalList.Add(New Dog("Woofie", 4))

Me.PetCollectionBindingSource.DataSource = DirectCast(animalList, System.ComponentModel.IBindingList)

End Sub

By default, this code should error when rendering the second pet, the Dog, because it can’t cast Dog as a Cat, rather than trying to cast Dog and Cat as Animal. To fix the problem, you need to strongly type the collection. I have found 3 workarounds to solve the problem.

1) Replace the PetList with a collection that from System.ComponentModel.BindingList(Of Animal)

2) Use a strongly typed Item accessor as follows:
Public ReadOnly Property Item(ByVal index As Integer) As Animal
Get
Return Me.InnerList.Item(index)
End Get
End Property

3) Implement System.ComponentModel.ITypedList as follows:
Public Function GetItemProperties(ByVal listAccessors() As System.ComponentModel.PropertyDescriptor) As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ITypedList.GetItemProperties

Return System.ComponentModel.TypeDescriptor.GetProperties(GetType(Animal))

End Function

Public Function GetListName(ByVal listAccessors() As System.ComponentModel.PropertyDescriptor) As String Implements System.ComponentModel.ITypedList.GetListName

Return "Animal"

End Function

Hopefully if you have run into this insidious error, the above information can help. Also, feel free to vote on this item at the feedback center: http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=7849a669-fe1e-469d-b02e-b36f3191c65d. I have included a sample application in that feedback item as well since I can’t upload sample projects to this blog at the moment.

Posted on - Comment
Categories: VB -
comments powered by Disqus