C# 升级到 .NET 4.5:ItemsControl 与其项目源不一致

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14336750/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:29:28  来源:igfitidea点击:

Upgrading to .NET 4.5: An ItemsControl is inconsistent with its items source

c#wpfdatagrid.net-4.5

提问by LasseBP

I'm building an application, which uses many ItemControls(datagrids and listviews). In order to easily update these lists from background threads I used this extension to ObservableCollections, which has worked fine:

我正在构建一个应用程序,它使用许多 ItemControls(数据网格和列表视图)。为了从后台线程轻松更新这些列表,我将此扩展用于 ObservableCollections,它运行良好:

http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx

http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx

Today I installed VS12(which in turn installed .NET 4.5), as I want to use a component which is written for .NET 4.5. Before even upgrading my project to .NET 4.5 (from 4.0), my datagrid started throwing InvalidOperationException when updated from a workerthread. Exception message:

今天我安装了 VS12(它又安装了 .NET 4.5),因为我想使用一个为 .NET 4.5 编写的组件。在将我的项目升级到 .NET 4.5(从 4.0)之前,我的数据网格在从工作线程更新时开始抛出 InvalidOperationException。异常信息:

This exception was thrown because the generator for control 'System.Windows.Controls.DataGrid Items.Count:5' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection. The following differences were detected: Accumulated count 4 is different from actual count 5. [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset).]

引发此异常的原因是名称为“(未命名)”的控件“System.Windows.Controls.DataGrid Items.Count:5”的生成器收到了与 Items 集合的当前状态不一致的 CollectionChanged 事件序列。检测到以下差异: 累计计数 4 与实际计数 5 不同。 [累计计数为(上次重置时的计数 + #Adds - 自上次重置以来的 #Removes)。]

Repro code:

复制代码:

XAML:

XAML:

<Window x:Class="Test1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
   <Grid>
      <DataGrid ItemsSource="{Binding Items, Mode=OneTime}" PresentationTraceSources.TraceLevel="High"/>       
   </Grid>
</Window>

Code:

代码:

public partial class MainWindow : Window
{
    public ExtendedObservableCollection<int> Items { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        Items = new ExtendedObservableCollection<int>();
        DataContext = this;
        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
            Task.Factory.StartNew(() =>
            {
                foreach (var item in Enumerable.Range(1, 500))
                {
                    Items.Add(item);
                }
            });                
    }
}

回答by Jehof

WPF 4.5 provides some new functionality to access collections on non-UI Threads.

WPF 4.5 提供了一些新功能来访问非 UI 线程上的集合。

It WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.

它 WPF 使您能够访问和修改线程上的数据集合,而不是创建集合的线程。这使您能够使用后台线程从外部源(例如数据库)接收数据,并在 UI 线程上显示数据。通过使用另一个线程来修改集合,您的用户界面将保持对用户交互的响应。

This can be done by using the static method EnableCollectionSynchronizationon the BindingOperationsclass.

这可以通过在类上使用静态方法EnableCollectionSynchronization来完成BindingOperations

If you have a lot of data to collect or modify, you might want to use a background thread to collect and modify the data so that the user interface will remain reactive to input. To enable multiple threads to access a collection, call the EnableCollectionSynchronization method. When you call this overload of the EnableCollectionSynchronization(IEnumerable, Object) method, the system locks the collection when you access it. To specify a callback to lock the collection yourself, call the EnableCollectionSynchronization(IEnumerable, Object, CollectionSynchronizationCallback) overload.

如果您有大量数据要收集或修改,您可能希望使用后台线程来收集和修改数据,以便用户界面保持对输入的反应。要启用多个线程访问集合,请调用 EnableCollectionSynchronization 方法。当您调用 EnableCollectionSynchronization(IEnumerable, Object) 方法的此重载时,系统会在您访问该集合时锁定该集合。要指定回调以自己锁定集合,请调用 EnableCollectionSynchronization(IEnumerable, Object, CollectionSynchronizationCallback) 重载。

The usage is as follows. Create an object that is used as a lock for the synchronization of the collection. Then call the EnableCollectionSynchronization method of the BindingsOperations and pass to it the collection you want to synchronize and the object that is used for locking.

用法如下。创建一个对象,用作集合同步的锁。然后调用 BindingsOperations 的 EnableCollectionSynchronization 方法并将要同步的集合和用于锁定的对象传递给它。

I have updated your code and added the details. Also i changed the collection to the normal ObservableCollection to avoid conflicts.

我已经更新了您的代码并添加了详细信息。我还将集合更改为正常的 ObservableCollection 以避免冲突。

public partial class MainWindow : Window{
  public ObservableCollection<int> Items { get; private set; }

  //lock object for synchronization;
  private static object _syncLock = new object();

  public MainWindow()
  {
    InitializeComponent();
    Items = new ObservableCollection<int>();

    //Enable the cross acces to this collection elsewhere
    BindingOperations.EnableCollectionSynchronization(Items, _syncLock);

    DataContext = this;
    Loaded += MainWindow_Loaded;
  }

  void MainWindow_Loaded(object sender, RoutedEventArgs e)
  {
        Task.Factory.StartNew(() =>
        {
            foreach (var item in Enumerable.Range(1, 500))
            {
                lock(_syncLock) {
                  Items.Add(item);
                }
            }
        });                
  }
}

See also: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

另见:http: //10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

回答by Chris

The answer from Jehof is correct.

杰霍夫的回答是正确的。

We cannot yet target 4.5 and had this issue with our custom observable collections that already allowed background updates (by using the Dispatcher during event notifications).

我们还不能以 4.5 为目标,并且我们的自定义 observable 集合已经允许后台更新(通过在事件通知期间使用 Dispatcher)存在此问题。

If anyone finds it useful, I have used the following code in our application that targets .NET 4.0 to enable it to use this functionality if the execution environment is .NET 4.5:

如果有人觉得它有用,我在我们的面向 .NET 4.0 的应用程序中使用了以下代码,如果执行环境是 .NET 4.5,它可以使用此功能:

public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
    // Equivalent to .NET 4.5:
    // BindingOperations.EnableCollectionSynchronization(collection, lockObject);
    MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) });
    if (method != null)
    {
        method.Invoke(null, new object[] { collection, lockObject });
    }
}

回答by VahidN

To summarize this topic, this AsyncObservableCollectionworks with .NET 4 and .NET 4.5 WPF apps.

总结本主题,这AsyncObservableCollection适用于 .NET 4 和 .NET 4.5 WPF 应用程序。

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;

namespace WpfAsyncCollection
{
    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        private static object _syncLock = new object();

        public AsyncObservableCollection()
        {
            enableCollectionSynchronization(this, _syncLock);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            using (BlockReentrancy())
            {
                var eh = CollectionChanged;
                if (eh == null) return;

                var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                  let dpo = nh.Target as DispatcherObject
                                  where dpo != null
                                  select dpo.Dispatcher).FirstOrDefault();

                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }

        private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
        {
            var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
                                    new Type[] { typeof(IEnumerable), typeof(object) });
            if (method != null)
            {
                // It's .NET 4.5
                method.Invoke(null, new object[] { collection, lockObject });
            }
        }
    }
}

回答by Nasheayahu

This is for Windows 10 Version 1607users using the release version of VS 2017that may have this issue.

这适用于使用可能存在此问题的VS 2017发行版的Windows 10 Version 1607用户。

Microsoft Visual Studio Community 2017
Version 15.1 (26403.3) Release
VisualStudio.15.Release/15.1.0+26403.3
Microsoft .NET Framework
Version 4.6.01586

You didn't need the locknor EnableCollectionSynchronization.

您不需要锁,也不需要EnableCollectionSynchronization

<ListBox x:Name="FontFamilyListBox" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Width="{Binding FontFamilyWidth, Mode=TwoWay}"
         SelectedItem="{Binding FontFamilyItem, Mode=TwoWay}"
         ItemsSource="{Binding FontFamilyItems}"
          diag:PresentationTraceSources.TraceLevel="High">
    <ListBox.ItemTemplate>
        <DataTemplate DataType="typeData:FontFamilyItem">
            <Grid>
                <TextBlock Text="{Binding}" diag:PresentationTraceSources.TraceLevel="High"/>

            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

public ObservableCollection<string> fontFamilyItems;
public ObservableCollection<string> FontFamilyItems
{
    get { return fontFamilyItems; }
    set { SetProperty(ref fontFamilyItems, value, nameof(FontFamilyItems)); }
}

public string fontFamilyItem;
public string FontFamilyItem
{
    get { return fontFamilyItem; }
    set { SetProperty(ref fontFamilyItem, value, nameof(FontFamilyItem)); }
}

private List<string> GetItems()
{
    List<string> fonts = new List<string>();
    foreach (System.Windows.Media.FontFamily font in Fonts.SystemFontFamilies)
    {
        fonts.Add(font.Source);
        ....
        other stuff..
    }
    return fonts;
}

public async void OnFontFamilyViewLoaded(object sender, EventArgs e)
{
    DisposableFontFamilyViewLoaded.Dispose();
    Task<List<string>> getItemsTask = Task.Factory.StartNew(GetItems);

    try
    {
        foreach (string item in await getItemsTask)
        {
            FontFamilyItems.Add(item);
        }
    }
    catch (Exception x)
    {
        throw new Exception("Error - " + x.Message);
    }

    ...
    other stuff
}