Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
See more: WPF ListBox DataTemplate , +
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
<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.
 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 27-Jun-13 5:07am
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 1

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:
 
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:
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:
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?
  Permalink  
Comments
Jason Gleim at 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 at 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.
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 2

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:
 
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:
 
<listbox name="CurrentListbox">
         VerticalAlignment ="Stretch" 
         Margin=" 8" 
         removed="Transparent" 
         Height="Auto"   
         BorderThickness="0" 
         HorizontalContentAlignment="Center" 
         FlowDirection="LeftToRight" 
         HorizontalAlignment="Center"
         ItemSource="{Binding ParentItemSource}">
 
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:
 
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:
 
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 Smile | :) )
 
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!
  Permalink  
Comments
kpolecastro at 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 at 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 at 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.
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 3

Imagine the items in triplicate for the different listboxes
<page.resources>
 
        <collectionviewsource>
              Source="{Binding Source={x:Static Application.Current}, Path=CurrentList}"   
              x:Key="CurrentList" />
 
The Function Reload Code
 
 
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
 
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
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
 
  Permalink  
Comments
kpolecastro at 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)

  Print Answers RSS
0 OriginalGriff 230
1 PIEBALDconsult 150
2 DamithSL 125
3 Andreas Gieriet 90
4 Jochen Arndt 90
0 OriginalGriff 5,790
1 DamithSL 4,601
2 Maciej Los 4,012
3 Kornfeld Eliyahu Peter 3,480
4 Sergey Alexandrovich Kryukov 3,195


Advertise | Privacy | Mobile
Web04 | 2.8.141220.1 | Last Updated 27 Jun 2013
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100