ComboBox Error: Collection was modified, enumeration operation may not execute

1

I have a combo box that holds a list of values followed by a static "Add New" item. When I select that item, it loads an image and adds the image's file name to the list of values. When I do this, however, the WPF underlying code throws the "collection modified" exception.

XAML:

<StackPanel Orientation="Vertical">
    <ComboBox x:Name="selector">
        <ComboBoxItem IsEnabled="False" Content="---" />
        <ComboBoxItem FontStyle="Italic" Content="Add New" Selected="New_Selected" />
    </ComboBox>
</StackPanel>

Code:

public partial class MainWindow : Window
{
    List<string> files = new List<string>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void RepopulateResourceSelector()
    {
        // Remove all but the bottom 2 items
        while (selector.Items.Count > 2)
        {
            selector.Items.RemoveAt(0);
        }

        int index = 0;

        // Add all strings in the list to combo box
        foreach (var file in files)
        {
            selector.Items.Insert(index, file);
            index++;
        }
    }

    private void New_Selected(object sender, RoutedEventArgs e)
    {
        var dlg = new OpenFileDialog();
        dlg.Filter = "Image Files (.bmp, .jpg, .gif, .png, .tiff)|*.bmp;*.jpg;*.gif;*.png;*.tiff";

        if (dlg.ShowDialog(this) == true)
        {
            // Add selected file to the list
            string name = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName);
            files.Add(name);

            RepopulateResourceSelector();
        }

        // Deselect `Add New` item
        selector.SelectedIndex = -1;
    }
}

Stack Trace:

System.InvalidOperationException occurred
    HResult=0x80131509
    Message=Collection was modified; enumeration operation may not execute.
Source=<Cannot evaluate the exception source>
StackTrace:
    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
    at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
    at System.Collections.Generic.List`1.Enumerator.MoveNext()
    at System.Windows.Controls.Primitives.Selector.SelectionChanger.CreateDeltaSelectionChange(List`1 unselectedItems, List`1 selectedItems)
    at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
    at System.Windows.Controls.Primitives.Selector.SelectionChanger.SelectJustThisItem(ItemInfo info, Boolean assumeInItemsCollection)
    at System.Windows.Controls.ComboBox.NotifyComboBoxItemMouseUp(ComboBoxItem comboBoxItem)
    at System.Windows.Controls.ComboBoxItem.OnMouseLeftButtonUp(MouseButtonEventArgs e)
    at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
    at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
    at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
    at System.Windows.Input.InputManager.ProcessStagingArea()
    at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
    at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
    at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
    at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
    at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Application.RunDispatcher(Object ignore)
    at System.Windows.Application.RunInternal(Window window)
    at System.Windows.Application.Run(Window window)
    at System.Windows.Application.Run()
    at WpfApp1.App.Main()
c#
wpf
combobox
asked on Stack Overflow Jun 11, 2017 by Abion47

2 Answers

0

You have a synchronisation problem with RepopulateResourceSelector(), that exception tells you that the Collection(which is an Enumeration) that you are iterating through is being modified at the same time in the same thread, that is why wrapping it with Dispatcher.BeginInvoke() solved your problem (here is a detailed explanation on how it works) and if I'm right, it could also be solved adding a lock, something like this:

private Object filesLock = new Object();

private void RepopulateResourceSelector()
{
    // Remove all but the bottom 2 items
    while (selector.Items.Count > 2)
    {
        selector.Items.RemoveAt(0);
    }

    int index = 0;

    lock(filesLock){
        // Add all strings in the list to combo box
        foreach (var file in files)
        {
            selector.Items.Insert(index, file);
            index++;
        }
    }
}
answered on Stack Overflow Jun 12, 2017 by Fabricio Monsalve
0

Instead of removing items from the ComboBox each time, you could use a CompositeCollection and an ObservableCollection to which you simply add the new items to.

Try this:

XAML:

<ComboBox x:Name="selector">
    <ComboBox.ItemsSource>
        <CompositeCollection>
            <CollectionContainer x:Name="cc" />
            <ComboBoxItem IsEnabled="False" Content="---" />
            <ComboBoxItem FontStyle="Italic" Content="Add New" Selected="New_Selected" />
        </CompositeCollection>
    </ComboBox.ItemsSource>
</ComboBox>

Code:

public partial class MainWindow: Window
{
    ObservableCollection<string> files = new ObservableCollection<string>();

    public MainWindow()
    {
        InitializeComponent();
        cc.Collection = files;
    }

    private void New_Selected(object sender, RoutedEventArgs e)
    {
        var dlg = new OpenFileDialog();
        dlg.Filter = "Image Files (.bmp, .jpg, .gif, .png, .tiff)|*.bmp;*.jpg;*.gif;*.png;*.tiff";

        if (dlg.ShowDialog(this) == true)
        {
            // Add selected file to the list
            string name = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName);
            Dispatcher.BeginInvoke(new Action(() =>
            {
                files.Add(name);
                // Deselect `Add New` item
                selector.SelectedIndex = -1;
            }));
        }
    }
}
answered on Stack Overflow Jun 12, 2017 by mm8

User contributions licensed under CC BY-SA 3.0