Las*_*eBP 26 c# wpfdatagrid .net-4.5
我正在构建一个应用程序,它使用许多ItemControls(datagrids和listviews).为了从后台线程轻松更新这些列表,我将此扩展用于ObservableCollections,它运行良好:
今天我安装了VS12(后来安装了.NET 4.5),因为我想使用为.NET 4.5编写的组件.在将我的项目升级到.NET 4.5(从4.0)之前,我的数据网格从workerthread更新时开始抛出InvalidOperationException.异常消息:
抛出此异常是因为控件'System.Windows.Controls.DataGrid Items.Count:5'的名称为'(未命名)'的生成器已收到与Items集合的当前状态不一致的CollectionChanged事件序列.检测到以下差异:累计计数4与实际计数5不同.[累计计数为(上次重置时计数+ #Adds - 自上次重置后自上次复位).
Repro代码:
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>
Run Code Online (Sandbox Code Playgroud)
码:
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);
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
Jeh*_*hof 39
WPF 4.5提供了一些访问非UI线程上的集合的新功能.
它使用WPF可以访问和修改除创建集合之外的线程上的数据集合.这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据.通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互.
这可以通过在类上使用静态方法EnableCollectionSynchronization来完成BindingOperations.
如果要收集或修改大量数据,则可能需要使用后台线程来收集和修改数据,以便用户界面对输入保持反应.要使多个线程能够访问集合,请调用EnableCollectionSynchronization方法.当您调用EnableCollectionSynchronization(IEnumerable,Object)方法的此重载时,系统会在您访问它时锁定该集合.要指定自己锁定集合的回调,请调用EnableCollectionSynchronization(IEnumerable,Object,CollectionSynchronizationCallback)重载.
用法如下.创建一个对象,该对象用作集合同步的锁.然后调用BindingsOperations的EnableCollectionSynchronization方法,并将要同步的集合和用于锁定的对象传递给它.
我已更新您的代码并添加了详细信息.此外,我将集合更改为正常的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);
}
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
另见:http:/ 10/ 10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
Vah*_*idN 11
总结此主题,这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 });
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jehof的答案是正确的.
我们还无法定位4.5,并且我们的自定义可观察集合已经允许后台更新(在事件通知期间使用Dispatcher)存在此问题.
如果有人发现它有用,我在我们的应用程序中使用了以下代码,它们以.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 });
}
}
Run Code Online (Sandbox Code Playgroud)