我建立一个应用程序,它使用许多ItemControls(数据网格和列表视图)。 为了方便地更新从后台线程这些名单我使用这个扩展ObservableCollections,这一直很好:
http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx
今天我装了VS12(这反过来又安装了.NET 4.5),因为我想用这对.NET 4.5编写的组件。 甚至我的项目升级到.NET 4.5(4.0)之前,我的DataGrid开始从的WorkerThread更新时抛出InvalidOperationException异常。 异常消息:
此异常被抛出,因为发电机控制“System.Windows.Controls.DataGrid Items.Count:5”名“(未命名)”已经收到了不与项目集合的当前状态同意CollectionChanged事件序列。 检测到如下的差异:累计计数图4是从实际计数5.不同[累计计数(在最后重置计数+ #Adds - 自上次复位#Removes)]
摄制代码:
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>
码:
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);
}
});
}
}
Answer 1:
WPF 4.5提供了一些新的功能,以在非UI线程访问集合。
它WPF使您能够在比创建集合的一个其他线程访问和修改数据集合。 这样就可以使用背景线程接收来自外部源的数据,例如数据库,并显示UI线程上的数据。 通过使用另一个线程来修改集合,您的用户界面保持响应用户的交互。
这可以通过使用静态方法来完成EnableCollectionSynchronization在BindingOperations
类。
如果你有大量的数据收集或修改,您可能需要使用一个后台线程来收集和修改数据,这样用户界面将保持反应的输入。 为了使多个线程访问集合,调用EnableCollectionSynchronization方法。 当调用EnableCollectionSynchronization(IEnumerable的,Object)方法的此重载,系统,当你访问它锁定的集合。 要指定一个回调到自己锁定集合,调用EnableCollectionSynchronization(IEnumerable的,对象,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);
}
}
});
}
}
参见: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
Answer 2:
总结这个话题,这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 });
}
}
}
}
Answer 3:
从Jehof答案是正确的。
我们还不能针对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 });
}
}
Answer 4:
这是Windows 10版1607个用户使用VS 2017年的发行版可能有这个问题。
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
你并不需要锁也不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
}
文章来源: Upgrading to .NET 4.5: An ItemsControl is inconsistent with its items source