Click here to Skip to main content
15,884,472 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more: , +
To anyone that can help or show me a better way. I have tried to do this with an observable collection, a list based on a custom class, global/non-global collections, using the listbox's itemssource, synclocking, and finally emptying and manually entering in items.

It tends to work fine, but every once in a while I get the error "Collection was modified; enumeration operation may not execute." which seems to occur at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()

I have tried a few different things over the past couple of weeks and I am to avoid it sometimes, but then I can reproduce it in a different area.

Essentially I have three listboxes Parent, Current, Children. These have captioned images in them.
When an Image is selected the images in the Parent, Current, and Children empty and reload based on the selected image.

I've written it in vb, but I can convert it to C# if that will help. The code has been changed many times and so the latest one has a lot of commented code in it. Any Help or suggestions would be greatly appreciated. Anything to simplify it, get it to work, or performance enhancements would be great.

The ListBox XAML is the same for all three listboxes with just a different name
XAML
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Disabled" PanningMode="VerticalOnly" FlowDirection="RightToLeft" HorizontalScrollBarVisibility="Auto"
                      >
                    <ListBox Name="CurrentListbox" VerticalAlignment ="Stretch" Margin=" 8" removed="Transparent" 
                              Height="Auto"   BorderThickness="0" HorizontalContentAlignment="Center" FlowDirection="LeftToRight" HorizontalAlignment="Center">
                        <ListBox.Resources>
                            <Style TargetType="{x:Type ListBoxItem}">
                                <EventSetter Event="ListBoxItem.Selected" Handler="CurrentListBoxItem_Selected" HandledEventsToo="false"/>
                    </Style>
                            </ListBox.Resources>
                        <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel IsItemsHost="True" />
                    </ItemsPanelTemplate>
                            </ListBox.ItemsPanel>
                        <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Border BorderBrush="#FF0000AC" Style="{StaticResource ResourceKey=ThumbnailBorder}">
                            <!--<Viewbox MaxWidth ="100" >-->
                            <StackPanel>

                                <Grid>
                                    <Border  Name="ItemBorder" Style="{StaticResource ResourceKey=ThumbnailInnerBorder}"/>
                                    <Image Name="PersonImage" MaxHeight ="200" MaxWidth ="200" Source="{Binding ProfileImagePath}"
                                                       HorizontalAlignment="Center" >
                                        <Image.OpacityMask>
                                            <VisualBrush Visual="{Binding ElementName=ItemBorder}"/>
                                        </Image.OpacityMask>
                                    </Image>
                                </Grid>
                                <Viewbox MaxWidth ="190" Margin="5,0,5,0" MaxHeight="15">
                                    <TextBlock Name="Person" Text="{Binding ProfileName}" Tag="{Binding UserID}" HorizontalAlignment="Center" />
                                </Viewbox>
                            </StackPanel>
                            <!--</Viewbox>-->
                        </Border>
                    </DataTemplate>
                            </ListBox.ItemTemplate>

                        </ListBox>
        
        </ScrollViewer>


When the image is selected it fires the following Code. It used to have observable collections, item source assignments etc.
VB
Private Sub Reload(ByVal CurrentID As Integer)
       SyncLock GetType(PlayPage)
           Try

               Try
                   For I = Me.ParentListbox.Items.Count - 1 To 0 Step -1
                       Me.ParentListbox.Items.RemoveAt(I)
                   Next
               Catch
               End Try
               Try
                   For I = Me.CurrentListbox.Items.Count - 1 To 0 Step -1
                       Me.CurrentListbox.Items.RemoveAt(I)
                   Next
               Catch
               End Try
               Try
                   For I = Me.ChildListbox.Items.Count - 1 To 0 Step -1
                       Me.ChildListbox.Items.RemoveAt(I)
                   Next
               Catch
               End Try


               CurrentPersonID = CurrentID
               'Load the Images Based on the people.
               Dim Ch = Load_Children_People(CurrentID)
               Dim C = Load_Current_People(CurrentID)
               Dim P = Load_Parent_People(CurrentID)
               Try
                   If P IsNot Nothing Then
                       For I = 0 To P.Count - 1
                           Me.ParentListbox.Items.Add(P(I))
                       Next
                   End If
               Catch
               End Try
               Try
                   If C IsNot Nothing Then
                       For I = 0 To C.Count - 1
                           Me.CurrentListbox.Items.Add(C(I))
                       Next
                   End If
               Catch
               End Try
               Try
                   If Ch IsNot Nothing Then
                       For I = 0 To Ch.Count - 1
                           Me.ChildListbox.Items.Add(Ch(I))
                       Next
                   End If
               Catch
               End Try


           Catch
           End Try

           'Thread.Sleep(200)
       End SyncLock
   End Sub


I could post more of the code, but I'm being yelled at for having a long post.

The Function in the background is grabbing the data from a SDF based on which information is relevant. The three functions are nearly identical with the exception of pulling different data.
Posted

I've seen similar issues, and managed to fix them by re-working the code.

The first change I would make would be with these:

VB
Try
  For I = Me.ParentListbox.Items.Count - 1 To 0 Step -1
         Me.ParentListbox.Items.RemoveAt(I)
  Next
Catch
End Try
Try
  For I = Me.CurrentListbox.Items.Count - 1 To 0 Step -1
          Me.CurrentListbox.Items.RemoveAt(I)
  Next
Catch
End Try
Try
   For I = Me.ChildListbox.Items.Count - 1 To 0 Step -1
          Me.ChildListbox.Items.RemoveAt(I)
   Next
Catch
End Try

That can all be replaced with:
VB
Me.ParentListbox.ItemSource = Nothing
Me.CurrentListbox.ItemSource = Nothing
Me.ChildListbox.ItemSource = Nothing


Next, I would just set the ItemSource's equal to the collection you are currently iterating through.
So:
VB
Me.ParentListbox.ItemSource = P
Me.CurrentListbox.ItemSource = C
Me.ChildListbox.ItemSource = Ch



It a) makes the code more readable, and b) saves doing lots of iterations and manual additions to the collection.

Now what I couldn't spot is where in your code you are actually getting the exception being thrown

yes I saw:
System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()


But that doesn't say what actually triggered the exception, what was going on at the time?
 
Share this answer
 
Comments
Jason Gleim 27-Jun-13 10:42am    
The exception is being caused by the individual element removal and addition to the listbox item collections. Those collections fire events EACH TIME he removes or adds an item. The UI will receive those events because they are bound to the collections in the control's code. Since all of that is async, you can get a situation where the item count of the underlying collection can change as the UI is iterating it and populating its display elements. That is a big no-no... you can't change a collection while it is being iterated or you will throw and exception.

What he should really do is work with an observable collections in the background and never mess with the listbox items collection directly. If you .Clear and .Add the backing collection, the listboxes will automatically update via the binding events.
kpolecastro 27-Jun-13 11:10am    
I've tried both of the ways that you guys suggest and I still receive the error.
The reason that I iterate and use the try is I was hoping to catch the error as it was processed. No Such Luck.
The error occurs after the entire Sub executes. I think there may be an issue with the UI, but I cannot be certain. When I add a thread.sleep command at the end of the reload function it will usually run fine. As a matter of fact, if I add a break point at the end of the reload function and then quickly let the code continue, it will never throw an error.
I'm going to expand my comment...

ItemSource is meant to be bound to an object that supports property change notification. What you should do is implement INotifyPropertyChanged on your class (I'm assuming the code-behind for that page) then add a property for each ItemSource:

C#
public ObservableCollection<object> ParentItemSource
{
  get
  {
    return _parentItemSource;
  }
  set
  {
    if(_parentItemSource != value)
    {
      _parentItemSource = value;
      RaisePropertyChanged("ParentItemSource");
    }
  }
}</object>


Of course your RaisePropertyChanged method will be whatever you name it and it simply raises the PropertyChanged event specified by INotifyPropertyChanged.

In the xaml, you now declaritively bind ItemSource:

XML
<listbox name="CurrentListbox">
         VerticalAlignment ="Stretch" 
         Margin=" 8" 
         removed="Transparent" 
         Height="Auto"   
         BorderThickness="0" 
         HorizontalContentAlignment="Center" 
         FlowDirection="LeftToRight" 
         HorizontalAlignment="Center"
         ItemSource="{Binding ParentItemSource}"></listbox>


The listbox will automagically hook to the backing collection. Anything you do to the collection will now be reflected (in a thread-safe manner) on the UI. From here, clearing the collection becomes:

C#
ParentItemSource = null;  // Set the backing property to null. Will fire notify event and the listbox will clear out.


Re-populating it becomes a matter of creating a temp collection via the for..each loop then assigning that collection to the public property:

VB
Dim P = Load_Parent_People(CurrentID)
Dim tempP = new ObservableCollection<object>()
Try
  If P IsNot Nothing Then
    For I = 0 To P.Count - 1
      tempP.Add(P(I))
    Next
  End If
  ParentItemSource = tempP 
Catch</object>


(Sorry for the jumping between languages :-) )

The important thing is that the binding mechanism separates the changes to the underlying collection from the UI. You HAVE to do that because UI updates are async due to the eventing mechanism. Bindings are there to take that problem away and provide an in-code, strongly-typed abstraction of the data.

Hope that helps!
 
Share this answer
 
Comments
kpolecastro 27-Jun-13 11:14am    
Thanks for the feedback. I will try this again, but this was actually the first method I tried based on the MS databinding sample. I'll give it another shot, maybe I missed something the first time around.
Jason Gleim 27-Jun-13 11:20am    
Really, you would probably be better off throwing all of the data lifting and those properties into it's own class (which implements INotifyPropertyChanged) and setting that class as the page's DataContext in the constructor of the code-behind. That is really how it is all designed to work.
kpolecastro 27-Jun-13 11:38am    
I reset it back to the original way I was doing things. I'll add it as a "Solution". I hope you find what I am missing because I am still getting the error.
Imagine the items in triplicate for the different listboxes
XML
<page.resources>

        <collectionviewsource>
              Source="{Binding Source={x:Static Application.Current}, Path=CurrentList}"   
              x:Key="CurrentList" />

    </collectionviewsource></page.resources>

<scrollviewer grid.row="1" verticalscrollbarvisibility="Disabled" panningmode="VerticalOnly" flowdirection="RightToLeft" horizontalscrollbarvisibility="Auto">
                      >
                    <listbox name="ParentListbox" verticalalignment="Stretch" margin=" 8" background="Transparent">
                               Height="Auto"   BorderThickness="0" HorizontalContentAlignment="Center" FlowDirection="LeftToRight" HorizontalAlignment="Center"
                             ItemsSource="{Binding ParentList}">
                        <listbox.resources>
                            <style targettype="{x:Type ListBoxItem}">
                                <eventsetter event="ListBoxItem.Selected" handler="ParentListBoxItem_Selected" handledeventstoo="False" />
                    </style>
                            </listbox.resources>
                        <listbox.itemspanel>
                    <itemspaneltemplate>
                        <wrappanel isitemshost="True" />
                    </itemspaneltemplate>
                            </listbox.itemspanel>
                        <listbox.itemtemplate>
                    <datatemplate>
                        <border borderbrush="#FF0000AC" style="{StaticResource ResourceKey=ThumbnailBorder}">
                            <!--<Viewbox MaxWidth ="100" >-->
                            <stackpanel>

                                <grid>
                                    <border name="ItemBorder" style="{StaticResource ResourceKey=ThumbnailInnerBorder}" />
                                    <image name="PersonImage" maxheight="100" maxwidth="100" source="{Binding ProfileImagePath}">
                                                       HorizontalAlignment="Center" >
                                        <image.opacitymask>
                                            <visualbrush visual="{Binding ElementName=ItemBorder}" />
                                        </image.opacitymask>
                                    </image>
                                </grid>
                                <viewbox maxwidth="90" margin="5,0,5,0" maxheight="15">
                                    <textblock name="Person" text="{Binding ProfileName}" tag="{Binding UserID}" horizontalalignment="Center" />
                                </viewbox>
                            </stackpanel>
                            <!--</Viewbox>-->
                        </border>
                    </datatemplate>
                            </listbox.itemtemplate>

                        </listbox>
        
        </scrollviewer>


The Function Reload Code

VB
Try
               
                ParentList = Nothing
                CurrentList = Nothing
                ChildrenList = Nothing

                

                CurrentPersonID = CurrentID
                'Load the Images Based on the people.
                Dim Ch = Load_Children_People(CurrentID)
                Dim C = Load_Current_People(CurrentID)
                Dim P = Load_Parent_People(CurrentID)

                ParentList = P
                CurrentList = C
                ChildrenList = Ch
               'For some reason, I need to assign the ItemsSource again

 Me.ParentListbox.ItemsSource = CollectionViewSource.GetDefaultView(ParentList)

                Me.CurrentListbox.ItemsSource = CollectionViewSource.GetDefaultView(CurrentList)
                Me.ChildListbox.ItemsSource = CollectionViewSource.GetDefaultView(ChildrenList)
              

            Catch
            End Try
           
    End Sub


The observable Collection Info

VB
Public Shared CurrentPeopleList As New CollectionViewSource
Public Shared Current_People_List As New ObservableCollection(Of Currents)()
Public Shared Property CurrentList() As ObservableCollection(Of Currents)
        Get
            Return Current_People_List
        End Get
        Set(ByVal value As ObservableCollection(Of Currents))
            Current_People_List = value
        End Set
    End Property


The Currents Class
VB
Public Class Currents
    Implements INotifyPropertyChanged

    Private ProfileNameValue As String

    Private UserIDValue As Integer

    Private ProfileImagePathValue As String

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

#Region "Properties Getters and Setters"
    Public Property ProfileName() As String
        Get
            Return Me.ProfileNameValue
        End Get
        Set(ByVal value As String)
            Me.ProfileNameValue = value
            'OnPropertyChanged("ProfileName")
        End Set
    End Property

    Public Property UserID() As Integer
        Get
            Return Me.UserIDValue
        End Get
        Set(ByVal value As Integer)
            If value < 0 Then
                Throw New ArgumentException("User ID must be greater than 0 ")
            End If
            Me.UserIDValue = value
            'OnPropertyChanged("UserID")
        End Set
    End Property

    Public Property ProfileImagePath() As String
        Get
            Return Me.ProfileImagePathValue
        End Get
        Set(ByVal value As String)
            Me.ProfileImagePathValue = RelativeProgramPath() & "Media\Pictures\" & value
            'OnPropertyChanged("ProfileImagePath")
        End Set
    End Property


#End Region

    Public Sub New(ByVal UserID As Integer, ByVal ProfileName As String, ByVal ProfileImagePath As String)
        Me.ProfileNameValue = ProfileName
        Me.UserIDValue = UserID
        Me.ProfileImagePathValue = If(ProfileImagePath Like "pack://*", ProfileImagePath, RelativeProgramPath() & "Media\Pictures\" & ProfileImagePath)

    End Sub

    Protected Sub OnPropertyChanged(ByVal name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
 
Share this answer
 
Comments
kpolecastro 27-Jun-13 11:53am    
Sorry, The OnPropertyChanged Code is not supposed to be commented out and not all the XAML showed up. I can post that separately if needed.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900