Listbox filter

asphughes

Member
Joined
Jan 10, 2007
Messages
17
Location
London
Programming Experience
3-5
I'm currently upgrading an application from VB to VB.net. The old application has a listbox that jumps to an item as the user types

e.g if you had a listbox of countries and type aus you would jump to Austria.

However with my .net version the listbox only filters on the last key

e.g if you type aus you would jump to s

Is there an easy way to make the .net listbox filter on all the keys pressed.

Thanks!
 
Where does the user type? Listbox doesn't support typing. There is autocomplete feature built into some .Net controls, like Textboxes and Comboboxes. You could start by looking into documentation for Textbox properties AutoCompleteMode, AutoCompleteSource and AutoCompleteCustomSource.

If you are using Key... events for Listbox you have to 'cache' the keyed input by some algoritm to enable a sequence of characters and at some interval clear to let user start a new sequence.
 
When the form loads the focus is set to the listbox the user simply begins typing and the listbox jumps to the first item beginning with the last letter typed.

I was hoping there might be a simple solution. I'll investigate autocomplete.

Thanks!
 
Ok, you're using some Key... event to catch the user key(s). It is simple. For each key you just add it to the 'string'. If you detect some time since last key you erase the 'string'. The 'string' you use to lookup.
VB.NET:
    Private Sub ListBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles ListBox1.KeyPress
        Static input As String
        Static lastinput As Date
        If Date.Now.Subtract(lastinput).Seconds > 0 Then
            input = e.KeyChar [COLOR=darkgreen]'starts new sequence[/COLOR]
        Else
            input &= e.KeyChar [COLOR=darkgreen]'add char to sequence[/COLOR]
        End If
        doLookup(input)
        lastinput = Date.Now
    End Sub
 
    Sub doLookup(ByVal input As String)
        [COLOR=darkgreen]'do lookup here[/COLOR]
        Me.Text = input [COLOR=darkgreen]'see what is cached![/COLOR]
    End Sub
 
Search the items, use the same code you had before, except now you have more than one letter, for example:
VB.NET:
    Sub doLookup(ByVal input As String)
        Dim itemstring As String
        For i As Integer = 0 To ListBox1.Items.Count - 1
            itemstring = ListBox1.Items(i)
            If itemstring.StartsWith(input) Then
                ListBox1.SelectedIndex = i
                Exit For
            End If
        Next
    End Sub
 
Thanks that works. I had not written any code. It was simply the default behaviour of the listbox in .net to only filter on the last key pressed.
 
There are a few strange bugs with the code that I have not been able to debug. When the searchstring ends with a 9 or 8 the selected item jumps to items beginning with 8 or 9.

Also if the searchstring ends with a zero the code selects the item one below the item the searchstring matches.

I can probably live with these bugs but it would be nice to fix them.
 
I can't see any bug, maybe you're not writing fast enough? Have you tried looking at the 'input' buffer at all times? Display it in form Text for example. Try to modify the reset time if you find it doesn't fit.
 
What is the 'input' filter string when the method fails and what items are in the list and what item does it choose? I don't think there is a bug with String.StartsWith method ;) Maybe you could post a small project that reproduces the problem?
 
Ok, here is an example of the problem. The listbox lists a some projects with a four digit prefix code. If the input string is 1478, then 888_Project2 is being selected out of the list below

1236_Project
1478_Project1
6796_Project2
8888_Project3
9003_Project4

I'll post a solution if I find one. Perhaps I am missing something obvious but I don't think so. The code again

VB.NET:
'filter project listbox by keys pressed
PrivateSub lstProject_KeyPress(ByVal sender AsObject, ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles lstProject.KeyPress
Static input AsString
Static lastinput AsDate
IfDate.Now.Subtract(lastinput).Seconds > 2 Then
input = e.KeyChar 'starts new sequence
Else
input &= e.KeyChar 'add char to sequence
EndIf
doLookup(input)
lastinput = Date.Now
EndSub
 
 
 
'filter project listbox by keys presses
Sub doLookup(ByVal input AsString)
Dim itemstring AsString
Dim lastchar AsString
lastchar = input.Substring(input.Length - 1)
txtMappings.Text = ""
For i AsInteger = 0 To lstProject.Items.Count - 1
itemstring = lstProject.Items(i).ToString()
If itemstring.StartsWith(input) Then
lstProject.SelectedIndex = i
EndIf
 
Next
Me.Text = input
EndSub
 
Last edited by a moderator:
Except for missing spaces the code looks the same. But the problem is finally revealed, and still unsolved. I replaced the doLookup with this also giving same wrong result:
VB.NET:
ListBox1.SelectedIndex = ListBox1.FindString(input)
Also tried exact match with string.Substring method, and while the match and index is correct the selectedindex gets wrong displayed!?
VB.NET:
If itemstring.Substring(0, input.Length) = input Then
    Me.Text = itemstring.Substring(0, input.Length) & " " & input & " " & i.ToString
    ListBox1.SelectedIndex = i
    Exit For
End If
 
Here's a variant that uses threading, it works for me with the test item set given. It just feels a little over the top for the task, but I see no other options now since the regular keypress seems to overload and break down for nothing.
VB.NET:
Private t As Threading.Thread
Private input As String

Private Sub ListBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles ListBox1.KeyPress
    Static lastinput As Date
    If Date.Now.Subtract(lastinput).Seconds > 0 Then
        input = e.KeyChar 'starts new sequence
    Else
        input &= e.KeyChar 'add char to sequence
    End If
    If Not t Is Nothing AndAlso t.IsAlive Then t.Abort()
    t = New Threading.Thread(AddressOf doLookup)
    t.Start()
    lastinput = Date.Now
End Sub

Private Sub doLookup()
    Dim itemstring As String
    For i As Integer = 0 To ListBox1.Items.Count - 1
        itemstring = ListBox1.Items(i)
        If itemstring.StartsWith(input) Then
            Dim d As New safesetlist(AddressOf setlist)
            ListBox1.Invoke(d, i)
            Exit For
        End If
    Next
End Sub

Private Delegate Sub safesetlist(ByVal i As Integer)
Private Sub setlist(ByVal i As Integer)
    ListBox1.SelectedIndex = i
End Sub
 
Thanks

That's a great improvement. Seems to work well with any combination of numbers. Still seems to be a problem for the letter 'o' for some reason. If an 'o' is in the input string then listbox will jump to item beginning with 'o'

i.e same problem I was having with 9 and 8 before your threading solution.
 
Back
Top