【问题标题】:ComboBox Error: Collection was modified, enumeration operation may not executeComboBox 错误:集合已修改,枚举操作可能无法执行
【发布时间】:2022-01-06 19:37:55
【问题描述】:

我有一个组合框,其中包含一个值列表,后跟一个静态“添加新”项。当我选择该项目时,它会加载图像并将图像的文件名添加到值列表中。但是,当我这样做时,WPF 底层代码会引发“集合已修改”异常。

XAML:

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

代码:

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;
    }
}

堆栈跟踪:

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()

【问题讨论】:

  • 这是因为您无法同时从selector 中删除它
  • @jamiedanq 我不会经历它。这是while 循环,而不是for
  • @jamiedanq 就像我在问题中所说的那样,我的代码中的任何地方都没有发生异常。它发生在 WPF 代码中的某处我的代码完成之后。
  • 异常堆栈跟踪中的前六个条目,它们告诉您什么?如果选择只是在改变,我不知道你为什么要尝试改变选择;但是,您可以在 Selected 和 SelectionChanged 事件结束后尝试修改选择。例如,通过调度调度器的事件队列(BeginInvoke 或 InvokeAsync)上的相应工作
  • 异常是否由于这一行RepopulateResourceSelector();或这一行selector.SelectedIndex = -1;而发生(即先注释掉其中一个,然后注释掉另一个,看看是哪个导致的)?可能是前者,但我只是想在给出建议之前检查一下。

标签: c# wpf combobox


【解决方案1】:

RepopulateResourceSelector() 存在同步问题,该异常告诉您正在迭代的 Collection(它是一个枚举)正在同一个线程中同时被修改,这就是为什么用 @ 包装它的原因987654323@ 解决了您的问题 (here is a detailed explanation on how it works),如果我是对的,也可以通过添加锁来解决,如下所示:

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++;
        }
    }
}

【讨论】:

  • 这不是线程问题,因为 WPF 默认只在一个线程上运行。因此,我不明白为什么加入锁会有帮助。就像我说的,异常不会在我自己的代码中的任何地方抛出。它在事件方法返回并且控制恢复到内部 WPF 代码后被抛出。
【解决方案2】:

您可以使用CompositeCollectionObservableCollection,而不是每次都从ComboBox 中删除项目,只需将新项目添加到其中即可。

试试这个:

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>

代码:

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;
            }));
        }
    }
}

【讨论】:

  • 查看我关于已经尝试过但它不起作用的评论。
  • 它对我来说很好用,所以你做错了什么。你真的复制并粘贴了我的代码吗?
  • 还要注意对 Dispatcher.BeginInvoke 的调用。
  • 当我尝试CompositeCollection 方法时,它没有抛出错误,但我的组合框根本没有更新。我没有做cc.Collection 分配,而是直接在CollectionContainer 上调用Add,所以这可能是我的问题。但无论如何,它现在正在工作,而且我已经转移到我程序的其他地方,所以我不想在这个阶段回溯以修复当前没有损坏的地方。
  • 你应该阅读你正在回答的问题的cmets。我已经说过,在您发布答案的 13 小时前,我有一个可行的解决方案。是的,你的答案有效,但我已经有了一个我很满意的解决方案,当我目前在代码的其他地方有更高的优先级要解决时,我不想回去用其他东西替换它。
【解决方案3】:

我有几乎完全相同的异常堆栈与 ListView(为触摸屏定制)

设置IsSynchronizedWithCurrentItem="False" 阻止它崩溃。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-05
    • 2015-06-17
    • 2020-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    相关资源
    最近更新 更多