Vb.Net - Combo/Listbox: ItemData and NewIndex

11/11/2009 15:13

Visual Basic .NET Tips and Tricks

I've been working in VB.NET for some time now, and here are some tips and tricks for those who are migrating from VB 6.0.


Combo/Listbox: ItemData and NewIndex

What kicks your butt in the beginning is simple little things. I remember when I first tried to insert items into a listbox and was hunting all over for the .AddItem method that would allow me to do so. When I found out that the VB.NET listbox had an internal Items collection that turned the Listbox.AddItem syntax into Listbox.Items.Add syntax, I was nonplussed at first. However, as you work with more of the classes in the Framework, you begin to see the consistency that was missing in VB 6.0. The items collection behaves the same as other collections in the framework, because they are derived from a common ancestor. Useful members like .AddRange, or .Contains are always there. And you never have to worry about using .ListCount instead of .Count (in .NET it will always be .Count) or think about whether the collection is zero or one based (always zero.)

At first I was missing two useful properties in the listbox/combobox. ItemData and NewIndex In VB 6.0 the .NewIndex property retrieves the index of the item added last. This can be done by checking the return value of the .Add method which returns the index. ItemData was useful because it could be used to save an integer value corresponding to the list item. This is handy when you have a numeric key to a column. When I realized that you could add ANY object to the Items collection, it became clear that this was a far more flexible approach and there wasn't anything lost at all in the translation ... Rather it was a .NET gain.
In order to add an object to the Listbox or combo box item collection, you must override the ToString method so that you will have a meaningful entry displayed in the list.


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        For i As Integer = 1 To 10
            'Index will hold the index of the newly added item.
            Dim Index As Integer = ListBox1.Items.Add(New ListItem(i, String.Format("Item {0}", i)))
            Debug.WriteLine(Index)  'will print the zero based index
        Next i

    End Sub

    Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
        'Cast it back to listitem class
        Dim ListItem As ListItem = DirectCast(ListBox1.SelectedItem, ListItem)
        Label1.Text = String.Format("ID={0}, Name={1}", ListItem.ID, ListItem.Name)
    End Sub
End Class
Public Class ListItem
    Private mID As Integer
    Private mName As String

#Region "Constructor(s)"
    Public Sub New(ByVal ID As IntegerByVal Name As String)
        mID = ID
        mName = Name
    End Sub

    Public Sub New()
        'Empty default constructor
    End Sub
#End Region

#Region "Properties"

    Public Property ID() As Integer
            Return mID
        End Get
        Set(ByVal Value As Integer)
            mID = Value
        End Set
    End Property

    Public Property Name() As String
            Return mName
        End Get
        Set(ByVal Value As String)
            mName = Value
        End Set
    End Property

    Public Overrides Function ToString() As String
        'Required so that the listbox will display the correct
        Return mName
    End Function

#End Region
End Class

Multiple column keys on hashtables

The Hashtable is the class that is most like a VB 6.0 collection. It allows you to retrieve an object with a key. Sometimes you want to key a hashtable on two values rather than one. In VB 6.0 one would combine the two elements into a string and use that as a key, but that is an ugly hack.

In VB.NET, as you can assign an object to the key, it seems easy enough to create an object to store the two values and use that as the key. However, when you do this, you have to override two of the objects methods: GetHashCode and Equals. If you don't override these methods, the hashtable will not recognise your key as a unique object. Although GetHashCode will return consistent results for repeated values of a simple data type, for objects, it does not rely on the state of the member variables. Overriding GetHashCode by "xor"ing the hashcodes of the internal variables will return a consistent result.

Because hash codes can still collide, it is necessary to override the Equals method to ensure that the hashtable can correctly tell when a key is unique to the collection. What we want is value equality rather than instance equality which is the default for object types. The documentation states also that if you override GetHashCode you must also override Equals for this reason. The documentation also explained that you could implement IComparable which would also work with a hashtable, although I never tried it.
Here is an example:

Public Class Key1
    Public X As String
    Public Y As Int32

    Public Sub New(ByVal s As StringByVal i As Int32)
        X = s
        Y = i
    End Sub

    Public Overrides Function GetHashCode() As Integer
        'Xor has the advantage of not overflowing the integer.
        Return X.GetHashCode Xor Y.GetHashCode

    End Function
    Public Overloads Overrides Function Equals(ByVal Obj As Object) As Boolean
        Dim oKey As Key1 = CType(Obj, Key1)
        Return (oKey.X = Me.X And oKey.Y = Me.Y)
    End Function

End Class