一个ItemsControl与其项源不一致 - WPF列表框

27

我有一个包含ListBox控件的WPF窗口,当执行按钮点击方法时,该控件会被填充。

XAML:

<ListBox Name="ThirdPartyListBox" ItemsSource="{Binding}" Margin="0,70,0,0">                      
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="C:\Users\Test\Desktop\Project\ACME-WPF\ACME-WPF\window-new-3.ico" Margin="5" Width="50"/>
                                <Button Name="ThirdPartyInstallButton" Content="Install" Click="InstallThirdPartyUpdatesButton_Click" Margin="5,5,0,0" Height="25"></Button>
                                <Button Name="ThirdPartyPostoneButton" Content="Postpone" Click ="PostponeThirdPartyUpdatesButton_Click" Margin="5,5,0,0" Height="25"></Button>
                                <TextBlock FontWeight="Bold" Text="{Binding Item2.Name}" Margin="12,25,0,0"/>
                                <TextBlock FontWeight="Bold" Text="{Binding Item2.RequiredVersion}" Margin="3,25,0,0"/>
                                <TextBlock Text="{Binding Item2.CustomUIMessage}" Margin="10,25,0,0" TextWrapping="Wrap" Foreground="Red"/>
                                <TextBlock Text="You have used " Margin="3,25,0,0"/>
                                <TextBlock Text="{Binding Item3.UsedDeferrals}" Margin="3,25,0,0"/>
                                <TextBlock Text=" of " Margin="3,25,0,0"/>
                                <TextBlock Text="{Binding Item2.MaxDefferals}" Margin="3,25,0,0"/>
                                <TextBlock Text=" deferrals for this update." Margin="3,25,0,0"/>
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>

C#:

 private void CheckforThirdPartyUpdatesButton_Click(object sender, RoutedEventArgs e)
    {
        CheckforThirdPartyUpdatesButton.IsEnabled = false;

        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += delegate(object s, DoWorkEventArgs args)
        {
            MainEntry.checkFor3PUpdates();
        };

        worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
        {

        };

        worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
        {

            ThirdPartyListBox.DataContext = RegScan_ThirdParty.comparisonListWithState;
            CheckforThirdPartyUpdatesButton.IsEnabled = true;
        };

        worker.RunWorkerAsync();
    }

到目前为止,所有都如预期一样运作,根据列表中有多少项,列表框将填充多行项目 ThirdPartyListBox.DataContext = RegScan_ThirdParty.comparisonListWithState;。但是,如果我与列表框项目有任何交互,就会引发一个“InvalidOperationException”异常,其中内部异常消息为“ItemsControl的项不一致。”

有人能帮我理解正在发生什么吗?

6个回答

32

当项目的源自另一个线程更改且 ListBox 没有收到关于 ItemsSource 更改的通知 (CollectionChanged 事件),就会抛出此类异常;因此,当它开始执行一些工作 (例如更新布局) 时,它将发现 Items 不等于 ItemsSource 并抛出异常。

自 .NET 4.5 起,WPF 提供了一种使集合从不同线程更改的方式。请在您的 ItemsSource 上尝试使用 EnableCollectionSynchronization 方法。在这个类似问题的回答中可以看到一个示例用法。


使用EnableCollectionSynchronization肯定是有帮助的,但并不能完全避免所有异常。在我的情况下,我使用Parallel.ForEach,虽然不像以前那样频繁地出现问题,但仍会引起一些问题。 - DJ van Wyk
4
如果这里也有一个例子的话会很好。 - tamj0rd2
2
对此感到相当恼火,基本上ObservableCollection只是MainThreadObservableCollection。 - AriesConnolly

30
您可以在已更改绑定资源的控件上简单调用Refresh()方法:
myListBox.Items.Refresh();

8

我也遇到了同样的错误。它是由于编辑作为ItemsSource使用的List中的对象造成的。

我的解决方案是将List替换为ObservableCollection。 根据.NET文档,ObservableCollection:

“表示一个动态数据集合,当项目被添加、移除或整个列表刷新时提供通知。”


这应该是被接受的解决方案。 - Graviton

1
我遇到了完全相同的错误信息,因为我在单个方法中多次(循环)清除并更新具有UI绑定的列表。

解决方案是创建一个临时列表,并仅分配新列表一次。
不是直接与您的问题相关,但是出现了相同的错误,所以我想在这里提一下...

0
我遇到了同样的错误“ItemsControl与其项源不一致”,但我是在UI线程上执行的所有操作。我使用了一个实现了ICollectionView接口的类作为ItemsSource。这个视图是我自己开发的。
问题在于视图在引发CollectionChanged事件之前没有使其从ICollectionView.GetEnumerator()返回的枚举器无效。ItemsSource在内部缓存了枚举器和使用它们得到的任何结果。如果枚举器没有被使无效,它将会愉快地重用它们和派生的值,包括项计数。
要使枚举器无效,你需要在其Current属性以及MoveNext()和Reset()方法中抛出InvalidOperationException异常。ItemsControl将会请求一个新的枚举器并重新创建缓存的数据。如果你的枚举器实现了IDisposable接口,它还会首先调用Dispose()方法。

-1
如果你的集合实现了INotifyCollectionChanged,你也可以发送NotifyCollectionChangedAction.Reset消息,基本上意思是“我搞砸了一切,让我们重新开始”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接