Finding Items
Rather than examining all of the code, let’s just look at a few interesting properties. The Item property illustrates indexing by number or by string, and its Property Get procedure has a quirky return value that changes depending on the argument.
Remember that the Item property is the default. It can be used for reading items:
Debug.Print srt(3)
Debug.Print srt("Lion")
Numeric indexing of XListBoxPlus works like using the List property of ListBox, except that it is one-based. String indexing does a lookup on the data:
Property Get Item(ByVal vIndex As Variant) As String
If VarType(vIndex) <> vbString Then
' For numeric index, return string value
Item = lst.List(vIndex - 1)
Else
' For string index, return matching index or 0 for none
Item = Match(vIndex)
If Item = 0 Then ErrRaise eseItemNotFound
End If
End Property
The Match function is used here and in many other parts of the implementation to do string lookups. How do you suppose it works? If you remember Chapter 6, you might want to use LookupItem, which is a wrapper for the SendMessage API function with the LB_FINDSTRING message. But this message is case insensitive, and that’s not what you need for searching some kinds of sorted data. Furthermore, Windows doesn’t know that the internal list box is sorted; the only way it can find an item is with a linear search. But you know that it’s sorted. The most efficient way to find a sorted item is with a bi-
nary search—the same one we talked about in Chapter 5. Here’s the code for Match:
Private Function Match(ByVal sItem As String) As Integer
Dim iPos As Integer
Select Case esmlMode
Case esmlUnsorted, esmlShuffle
Match = LookupItem(lst, sItem) + 1
Case Else ' Some kind of sorting
If BSearch(sItem, iPos) Then Match = iPos + 1 Else Match = 0
End Select
End Function
Either way, Match will come up with an index to the item, if it exists. Match is also used in the Item Property Let, which enables you to assign items:
.Item(3) = "Deer"
.Item("Lion") = "Big Cat"
It works this way:
Property Let Item(ByVal vIndex As Variant, sItemA As String)
' For string index, look up matching index
If VarType(vIndex) = vbString Then
vIndex = Match(vIndex)
' Fail if old item isn't found or if new item is found
If vIndex = 0 Then ErrRaise eseItemNotFound
If Match(sItemA) Then ErrRaise eseDuplicateNotAllowed
End If
' Assign value by removing old and inserting new
Remove vIndex
Add sItemA
End Property
This code converts a string index to a numeric index and then uses the Remove and Add methods to assign it in the proper sort order. I could go on to show how Remove finds and deletes items, but the code is the same as the Item Property Get. The Add method is more interesting:
Sub Add(sItem As String, Optional iPos As Integer = 1)
With lst
' Adding differs depending on the mode
Select Case esmlMode
Case esmlUnsorted
' Add where directed (start is default)
.AddItem sItem, iPos - 1
Case esmlShuffle
' Add at random position
iPos = GetRandom(0, .ListCount - 1)
If .ListCount Then
.AddItem sItem, iPos
Else
.AddItem sItem
End If
Case Else ' Some kind of sorting
' Binary search for the item
If BSearch(sItem, iPos) Then
ErrRaise eseDuplicateNotAllowed
Else
' Insert at sorted position
If .ListCount Then
.AddItem sItem, iPos
Else
.AddItem sItem
End If
End If
End Select
End With
End Sub
Add recognizes the optional position argument in unsorted modes, but ignores it if the data is sorted. The data is always inserted at the correct position so that the list remains sorted. The only time you actually have to sort the data is if you change the sort mode. Notice that the code raises an error if you try to insert an item that already exists into a sorted list. Duplicate items could have been permitted, but I made a design judgment that most applications that want sorted lists would not want duplicates.
There’s a lot more to XListBoxPlus, but I’ll leave you to explore the rest.