tags:

views:

133

answers:

4

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 contain the string. Here is what I have tried:

Dim Item As ListItem
For Each Item In CtheList.Items
    If Item.Text.IndexOf("W:") = -1 Then
        CtheList.Items.Remove(Item)
     End If
 Next

Which is apparently a no-no as it generates the error: Collection was modified; enumeration operation may not execute.

I have also tried:

Dim Item As ListItem
For Each Item In CtheList.Items
    If Item.Text.IndexOf("W:") = -1 Then
        Dim i As Integer
        For i = 0 To CtheList.Items.Count - 1
            If CtheList.Items.Item(i).Text.IndexOf("W:") > -1 Then
                CtheList.Items.RemoveAt(i)
            End If
        Next i
    End If
Next

Which generates an index out of range exception.

Any help is greatly appreciated.

+3  A: 

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.

balpha
Yeah, that would be the way to do it, but unfortunately still causes the Collection was modified error. +1 for your help though.
Jim
Yes, your first way will always cause this error. You're simply not allowed to modify the object you're iterating over. I was talking about reversing the loop in your second way.
balpha
Also in my second example I am still modifying the list while iterating over it, just in a 2nd loop. My solution is below, but you still get the check mark as you led me to the answer.
Jim
Oh, right, I never noticed that. Conceded :-)
balpha
A: 

Why don't you filter the items before they are placed in the listbox?

ZippyV
+1  A: 

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
Jim
Isn't having two passes through this redundant? If you're going to loop through it anyway then you might as well do it all in one pass. Your 2nd loop alone, without the boolean check, is sufficient.
Ahmad Mageed
My bad. Updated example.
Jim
Actually, on second glance, my code is correct. I am only filtering out the ListItems with a "W" if there is a ListItem that doesn't contain a "W". My 2nd loop alone would get rid of all "W" ListItems, regardless of whether a non-W ListItem is present. Updated example again.
Jim
+2  A: 

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()
Ahmad Mageed
+1 for your insight. Unfortunately no LINQ for me as this is .NET 1.1 but still good to know.
Jim
Something to look forward to then :)
Ahmad Mageed