WinForm / ComboBox / Custom Collection/Objects

RobstaMeade

New member
Joined
Aug 3, 2011
Messages
4
Programming Experience
10+
Hi,

I have a custom collection which is derived from a base class implementing IEnumerable and IEnumerator, this collection is populated with custom objects.

I'm trying to set the datasource on a ComboBox on a WinForm to use the above collection - but when the code is run the ComboBox is always empty.

The code I was using was this:

Private Sub ViewSources_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
' declaration
Dim sourceCollection As SourceCollection
Dim source As Source
' instantiate
sourceCollection = sourceCollection.GetSources
ViewSources_Source_ComboBox.DataSource = sourceCollection
ViewSources_Source_ComboBox.DisplayMember = "Name"
ViewSources_Source_ComboBox.ValueMember = "Name"
' housekeeping
HouseKeeping.Dispose(sourceCollection)
End Sub

As I couldn't get that to work, I tried this:

Private Sub ViewSources_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
' declaration
Dim sourceCollection As SourceCollection
Dim source As Source
' instantiate
sourceCollection = sourceCollection.GetSources
For Each source In sourceCollection
ViewSources_Source_ComboBox.Items.Add(source)
Next
ViewSources_Source_ComboBox.DisplayMember = "Name"
ViewSources_Source_ComboBox.ValueMember = "Name"
' housekeeping
HouseKeeping.Dispose(sourceCollection)
End Sub
Can anyone explain to me why the first chunk of code didn't work? I tend to do more coding with web than windows and I'm pretty sure I've used the first code example on a DropDownList control previously and its worked?

Any information/help is appreciated,

Regards

Rob
 
Last edited:
Why are you calling Dispose on 'sourceCollection'? Disposing is something you do to an object when you are finished using it. If you have a collection bound to a control then obviously you are not finished using it so you should not be disposing it. I'm guessing that the Dispose method of your collection removes all the items.

That said, why would you bother removing the items from the collection anyway? There's no point. I'm guessing that you have done way more work than you need to in creating that custom collection class. In general, this is all you need to do to create a custom collection
VB.NET:
Public Class ThingCollection
    Inherits System.Collections.ObjectModel.Collection(Of Thing)
End Class
All the standard behaviour, i.e. the IList and IList(Of T) implementation, is inherited. If you need custom behaviour then you add or override members as required.
 
Hi,

Thank you for your reply.

Regarding the HouseKeeping.Dispose() method - this is just a method used to .Close any objects that require it, prior to it setting them to = Nothing, it doesn't do anything specific for that collection, it'll just make that object = Nothing. I typically do this for anything I've instantiated in a code block.

I took on board what you said, about it being DataBound and then I'm setting it to = Nothing, so I commented out that line and it still didn't work, it just doesn't put anything in the ComboBox, as opposed to my latter code example which does (and still uses the HouseKeeping.Dispose() method etc).

Thank you for the information regarding the IList, the reason I have this custom collection is that I knocked up the basic structure for this a few years ago and tend to just re-use code where-ever I can, as I know this collection works fine, and has all of the methods/properties I need (data mapping from dataSets, .Count, .RemoveAt, .Clear etc etc) - so I tend to stick with it. IIRC, way back when I wrote it, I was looking for a way to use a For...Each on a collection and was having some difficulties using what I found at that time, admittedly I can't remember what that was at the time, think I was trying to use the .Net Collection object? Does IList support For...Each, or only index based getting/setting of items?

Thanks again for taking the time to reply,

Kind regards

Rob
 
Having that Dispose call there makes no sense. If all it's doing is setting the reference to Nothing then it's pointless because it's a local variable. Local variables cease to exist at the end of the method so setting them to Nothing serves no purpose. The parameter would have to be ByRef for it to actually set the original variable to Nothing anyway. If it's doing more than that then it's worse than pointless, because you're effectively destroying an object that you obviously still want to use. Don't add cleanup code arbitrarily. Do something because you know it should be done.

As for the issue, I'll wager that there's an exception being thrown but you aren't being notified because it's in the Load event handler, which swallows exceptions. Wrap the code in an exception handler and you'll probably find the reason.

In order to support enumeration, i.e. For Each loops, an object's type must implement the IEnumerable interface. The IList interface extends the ICollection interface, which extends IEnumerable. As such, any IList object is inherently an IEnumerable object and supports For Each loops. In actual fact, it's IList that is the requirement for data-binding in WinForms.
 
Hi,

Thanks again for your reply.

Regarding the local variable comment you mentioned, are you saying that if this was for example a StreamReader/Writer, you wouldn't bother to .Close and = Nothing it? I thought it was good practice to tidy up after one's self, closing connections, and so on... ? When you say "local variables with cease to exist at the end of the method", are you referring to the .Net garbage collection etc?

Regarding the IList - so I can implement IEnumerator also? Thus having for example: For Each <myObject> In... If so, wonder how I missed this all those years ago, not sure it really makes much difference at the moment being that the collection stuff I have is working, but it is interesting to know, I might well take a look and see if it'll take much to change over what I have to IList, perhaps its the fact that my collection object doesn't derive from an IList in the first place that is causing the data binding problem?


The Try/Catch - you were spot on - it threw this:
"Complex DataBinding accepts as a data source either an IList or an IListSource"

Thanks again for your reply,

Rob
 
Regarding the local variable comment you mentioned, are you saying that if this was for example a StreamReader/Writer, you wouldn't bother to .Close and = Nothing it? I thought it was good practice to tidy up after one's self, closing connections, and so on... ? When you say "local variables with cease to exist at the end of the method", are you referring to the .Net garbage collection etc?
No, I'm not suggesting that. If you create a StreamReader, StreamWriter or any other disposable object within a method and you finish with it before the end of the method you should absolutely dispose it. You wouldn't dispose it if you wanted to use the object after the method ends though. Setting the variable to Nothing is pointless too, because, as I said, the variable will cease to exist at the end of the method. The reason you set a variable to Nothing is to release the reference so that the object can be cleaned up by the garbage collector. When the method ends and the variable ceases to exist though, the reference is released anyway.
Regarding the IList - so I can implement IEnumerator also? Thus having for example: For Each <myObject> In... If so, wonder how I missed this all those years ago, not sure it really makes much difference at the moment being that the collection stuff I have is working, but it is interesting to know, I might well take a look and see if it'll take much to change over what I have to IList, perhaps its the fact that my collection object doesn't derive from an IList in the first place that is causing the data binding problem?


The Try/Catch - you were spot on - it threw this:
"Complex DataBinding accepts as a data source either an IList or an IListSource"
We have our answer then. As I said, IList is the requirement for WinForms data-binding. If your class only implements IEnumerable then you can't bind it to a WinForms control. I suggest that you ditch your old class and either do as I suggested earlier or else simply use a generic List(Of T).

If you're determined to stick with your current class then the alternative would be to call ToList or, preferably, ToArray on your IEnumerable to return a List(Of T) or an array, both of which implement IList, which you can then bind.

By the way, you haven't actually implemented IEnumerable and IEnumerator in the same class have you? The usual way is to declare a type that implements IEnumerator nested inside the type that implements IEnumerable. Also, in this day and age, you should also have generic interface implementations, i.e. IEnumerable(Of T), or better IList(Of T), and IEnumerator(Of T).
 
Hi again,

Ok, I'm getting a bit hung up on this "variable" you mention, can you run it through a bit more for me? In the code examples I posted, sourceCollection was the variable - referencing the SourceCollection object instance. Whilst the object (the custom collection) doesn't have any requirements for a .Close etc, I had always assumed that the = Nothing (from the HouseKeeping.Dispose(sourceCollection) call) was appropriate. You yourself said that this releases the reference so that the object can be cleaned up by the garbage collector. Isn't this exactly what I'm doing? If in that same function I'd just dimmed in a variable, lets say a tring, set it to equal "Rob", I'd not bother at the end of the function calling the HouseKeeping.Dispose(myVariable) - I only use that for the things I've "new"'d in - which are always objects... ? /scratches head...

Regarding the IEnumerable/IEnumerator in the same class - I believe I found the base of this in an example - most likely from the Microsoft MSDN site.... here's the full code for this class, i've not chopped out any bits for readability, so its a bit lengthly I'm afraid.... The one thing I've found very handy with this class, was that the collection can be of anything - but what I have is a series of "specific" object related collections classes that inherit this as a base class... so that SourceCollection class above, is a very small class indeed, inherits the code below, I have methods for each of these "specific" collections such as "AddSource" which would subsequently take in a "Source" object, and stuff it in the base collection (which doesn't care what type it actually is).



Namespace UHBristol.LogFileMuncher.Business.Collection
Public Class CollectionBase
Implements Global.System.Collections.IEnumerable, Global.System.Collections.IEnumerator
#Region " Fields "
Private _collection As ArrayList
Private _currentPosition As Integer
#End Region
#Region " Instantiation "
''' <summary>
''' Constructor, creates a new arraylist and calls the Reset() method, setting _currentPosition to -1.
''' </summary>
''' <remarks></remarks>
Protected Sub New()
' instantiate
_collection = New ArrayList
' reset the collection
Reset()
End Sub
#End Region
#Region " Properties "
''' <summary>
''' Returns the count of items within the collection
''' </summary>
''' <value>Integer</value>
''' <returns>The current count of items within the collection</returns>
''' <remarks></remarks>
Public ReadOnly Property Count() As Integer
Get
Return _collection.Count
End Get
End Property
''' <summary>
''' Returns the current enumerator
''' </summary>
''' <value>Object</value>
''' <returns>The current enumerator</returns>
''' <remarks></remarks>
Public ReadOnly Property Current() As Object Implements Global.System.Collections.IEnumerator.Current
Get
' return object
Return _collection(_currentPosition)
End Get
End Property
''' <summary>
''' Gets or sets the current enumerator's index value in the collection
''' </summary>
''' <value>Integer</value>
''' <returns>The current enumerator's index</returns>
''' <remarks></remarks>
Public Property CurrentIndex() As Integer
Get
Return _currentPosition
End Get
Set(ByVal value As Integer)
_currentPosition = value
End Set
End Property
''' <summary>
''' Returns an enumerator for the specified index value
''' </summary>
''' <param name="index">The index value for the required enumerator</param>
''' <value>Object</value>
''' <returns>An enumerator for the specified index value</returns>
''' <remarks></remarks>
Default Public Overridable ReadOnly Property Item(ByVal index As Integer) As Object
Get
Return _collection(index)
End Get
End Property
#End Region
#Region " Methods "
''' <summary>
''' Adds an object to the collection
''' </summary>
''' <param name="collectionItem">The object to add to the collection</param>
''' <remarks></remarks>
Protected Sub Add(ByVal collectionItem As Object)
' populate
_collection.Add(collectionItem)
End Sub
''' <summary>
''' Clears all objects from the collection
''' </summary>
''' <remarks></remarks>
Protected Sub Clear()
' clear items
_collection.Clear()
End Sub
''' <summary>
''' Gets the enumator
''' </summary>
''' <returns>Return IEnumerator</returns>
''' <remarks></remarks>
Protected Function GetEnumerator() As Global.System.Collections.IEnumerator Implements Global.System.Collections.IEnumerable.GetEnumerator
' return enumerator
Return Me
End Function
''' <summary>
''' Provides a method to do further processing whilst mapping objects from the provided dataSet
''' </summary>
''' <param name="dataSet">The dataSet to be parsed</param>
''' <returns>True/False</returns>
''' <remarks></remarks>
Public Overridable Function MapObjects(ByVal dataSet As DataSet) As Boolean
' declaration
Dim result As Boolean
If IsNothing(dataSet) = False Then
If dataSet.Tables.Count > 0 Then
' populate
result = MapObjects(dataSet.Tables(0))
Else
' populate
result = False
End If
Else
' populate
result = False
End If
' return result
Return result
End Function
''' <summary>
''' Provides a method to do further processing whilst mapping objects from the provided dataTable
''' </summary>
''' <param name="dataTable">The dataTable to be parsed</param>
''' <returns>True/False</returns>
''' <remarks></remarks>
Public Overridable Function MapObjects(ByVal dataTable As DataTable) As Boolean
' declaration
Dim result As Boolean
' could do stuff here if needed..
' populate
result = True
' return result
Return result
End Function
''' <summary>
''' Moves to the next item in the colleciton
''' </summary>
''' <returns>True if able to move to the next item, False if at end of collection</returns>
''' <remarks></remarks>
Protected Function MoveNext() As Boolean Implements Global.System.Collections.IEnumerator.MoveNext
' increment variable
_currentPosition += 1
' return boolean
Return (_currentPosition < (_collection.Count))
End Function
''' <summary>
''' Removes an item from the collection at the specified index position
''' </summary>
''' <param name="index">The index position to remove an item from</param>
''' <remarks></remarks>
Public Sub RemoveAt(ByVal index As Integer)
' remove
_collection.RemoveAt(index)
End Sub
''' <summary>
''' Resets the _currentPosition value to -1
''' </summary>
''' <remarks>This should be called at the end of any iteration of an inherited collection object if it is necessary to iterate through the same object again</remarks>
Public Sub Reset() Implements Global.System.Collections.IEnumerator.Reset
' populate
_currentPosition = -1
End Sub
''' <summary>
''' Sorts the elements in the entire arraylist
''' </summary>
''' <remarks></remarks>
Public Sub Sort()
' sort
_collection.Sort()
End Sub
''' <summary>
''' Sorts the elements in the entire arraylist using the specified comparer
''' </summary>
''' <param name="iComparer">The IComparer implentation to use when comparing elements</param>
''' <remarks></remarks>
Public Sub Sort(ByVal iComparer As Global.System.Collections.IComparer)
' sort
_collection.Sort(iComparer)
End Sub
''' <summary>
''' Sorts the elements in a range of elements in the arraylist using the specified comparer
''' </summary>
''' <param name="index">The zero-based starting index of the range to sort</param>
''' <param name="count">The length of the range to sort</param>
''' <param name="iComparer">The IComparer implentation to use when comparing elements</param>
''' <remarks></remarks>
Public Sub Sort(ByVal index As Integer, ByVal count As Integer, ByVal iComparer As Global.System.Collections.IComparer)
' sort
_collection.Sort(index, count, iComparer)
End Sub
#End Region
End Class
End Namespace

 
Back
Top