I am trying to filter a ListBox based on the presence of a string. Basically, if there is a ListItem that doesn\'t contain the string then I want to remove all ListItems that do
Why don't you filter the items before they are placed in the listbox?
When removing items from a list there are a couple of options. As you've discovered, modifying the collection in a foreach loop isn't going to work. A for loop that counts down is the answer as @balpha mentioned.
Another option is to store a list of items in a separate list, then iterate over that to remove items from the original list. Yet another option is to use LINQ.
Sample list:
Dim stringList As New List(Of String)
stringList.Add("W:foo")
stringList.Add("bar")
stringList.Add("barW:")
stringList.Add("foo")
Reverse For Loop
For i As Integer = stringList.Count - 1 To 0 Step -1
If stringList(i).IndexOf("W:") > -1 Then stringList.RemoveAt(i)
Next
ForEach with 2 Lists
Dim removeList As New List(Of String)
' store items to remove here
For Each s As String In stringList
If s.IndexOf("W:") > -1 Then removeList.Add(s)
Next
' remove stored items here
For Each s As String In removeList
stringList.Remove(s)
Next
LINQ
In this snippet I filter on IndexOf = -1 instead of > -1 to keep what I want rather than filter what I don't want.
stringList = stringList.Where(Function(s) s.IndexOf("W:") = -1).ToList()
Try reversing your loop, i.e. start from the end of the list. That way, deleting items won't shift the index of the remaining items you still have to check (which is the cause of your out of range exception).
The first way causes a problem because you're modifying the list while iterating over it. And that is, as you said, a big no-no.
Based on balpha's help above, this is what I ultimately did:
Dim StringPresent As Boolean = False
Dim Item As ListItem
For Each Item In CtheList.Items
If Item.Text.IndexOf("W:") = -1 Then
StringPresent = True
Exit For
End If
Next
If StringPresent = True Then
Dim i As Integer
For i = CtheList.Items.Count - 1 To 0 Step -1
If CtheList.Items.Item(i).Text.IndexOf("W:") > -1 Then
CtheList.Items.RemoveAt(i)
End If
Next i
End If
Let suppose the filter string is a textbox string and you want to filter your listbox according to the text changed in textbox. Let the name of the textbox is TB_Filter
.
Place this line of code from the top of your codes, after Public Class Form1
to temporary hold your listbox items.
Dim TempHoldMyItems As New ArrayList
Now use this piece of codes in your form_load event to update the temporary list from your listbox items.
TempHoldMyItems.Clear()
TempHoldMyItems.AddRange(listbox1.Items)
Ok, Now you can filter your listbox items according to the text in TB_Filter. Use this codes in TB_Filter Text Changed event.
listbox1.Items.Clear()
For Each item As String In TempHoldMyItems
If item.Length >= TB_Filter.Text.Length Then
If item.Substring(0, TB_Filter.Text.Length) = TB_SearchInSelected.Text Then
listbox1.Items.Add(item)
End If
End If
Next
This will filter items in listbox1 as you will type in TB_Filter. This will match starting of your items with your filter word in TB_Filter.
By making little changes (i.e. Instead of substring
you may use contains
property) to check filter query is contains in item.
listbox1.Items.Clear()
For Each item As String In TempHoldMyItems
If item.Contains(TB_Filter.Text) Then
listbox1.Items.Add(item)
End If
Next