Looking up list box items
The Visual Basic ListBox control (and related controls such as ComboBox and FileListBox) provide no direct way to look up the index of an item from its name. There is a workaround using the Text property (see “Collection Methods and Properties,” Chapter 11), but it’s not as flexible as you might like.
The Windows API provides a way to look up items by name using the LB_FINDSTRING message. It takes only one line of code to send this message and just a few more lines to put the whole thing in a neat little wrapper:
#If fComponent Then
Function LookupItem(ctl As Object, sItem As String) As Long
#Else
Function LookupItem(ctl As Control, sItem As String) As Long
#End If
If TypeName(ctl) = "ComboBox" Then
LookupItem = SendMessageStr(ctl.hWnd, CB_FINDSTRING, -1&, sItem)
Else
LookupItem = SendMessageStr(ctl.hWnd, LB_FINDSTRING, -1&, sItem)
End If
End Function
The key to this code is the LB_FINDSTRING message constant (and the CB_FINDSTRING message for combo boxes). The Windows API documentation tells you that the wParam value contains either the index of the item just before the item where the search should start or -1 to search the entire list. The lParam value contains a pointer to the case-insensitive string being sought. You can pass a partial string so that “BIG” will find “BigDeal” or “Big Brother”, whichever comes first. The return value is the index of the found item (or -1 if none is found). Windows also provides an LB_FINDSTRINGEXACT (and CB_FINDSTRINGEXACT) message for looking up the full case-sensitive string.
Notice that my LookupItem function uses conditional compilation to receive either a Control parameter or an Object parameter, depending on whether the function is located in a component. I want LookupItem to work with any ListBox-like control, so its first parameter can’t be more type-specific. I’d also like to avoid the late-binding penalties that come with the Object type but, unfortunately, Visual Basic won’t let you define a Control parameter in a public DLL procedure. Because this function allows you to pass in any control, be sure not to pass one whose underlying Windows control doesn’t support the LB_FINDSTRING (or CB_FINDSTRING) message. To be safe, this function should use error trapping to trap message failure, but I compromised. Perhaps you shouldn’t.
Before I learned about the LB_FINDSTRING message, I looked up items with a linear search. That’s still the way you have to look up items in the ItemData array. Visual Basic implements its ItemData feature using the LB_GETITEMDATA and LB_SETITEMDATA messages, but it doesn’t provide an LB_FINDITEMDATA message. You have to do it the hard way:
#If fComponent Then
Function LookupItemData(ctl As Object, data As Long) As Integer
#Else
Function LookupItemData(ctl As Control, data As Long) As Integer
#End If
Dim i As Integer
LookupItemData = -1
For i = 0 To ctl.ListCount - 1
If data = ctl.ItemData(i) Then
LookupItemData = i
Exit Function
End If
Next
End Function