[英]An ItemsControl is inconsistent with its items source - WPF Listbox
[英]Upgrading to .NET 4.5: An ItemsControl is inconsistent with its items source
我正在構建一個應用程序,它使用許多 ItemControls(數據網格和列表視圖)。 為了從后台線程輕松更新這些列表,我使用了 ObservableCollections 的這個擴展,它運行良好:
今天我安裝了 VS12(它又安裝了 .NET 4.5),因為我想使用一個為 .NET 4.5 編寫的組件。 在將我的項目升級到 .NET 4.5(從 4.0)之前,我的數據網格在從工作線程更新時開始拋出 InvalidOperationException。 異常信息:
拋出此異常是因為名稱為“(未命名)”的控件“System.Windows.Controls.DataGrid Items.Count:5”的生成器已收到與 Items 集合的當前 state 不一致的 CollectionChanged 事件序列。 檢測到以下差異:累計計數 4 與實際計數 5 不同。[累計計數為(上次復位計數 + #Adds - #Removes since last Reset)。]
復制代碼:
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);
}
});
}
}
WPF 4.5提供了一些訪問非UI線程上的集合的新功能。
它使用WPF可以訪問和修改除創建集合之外的線程上的數據集合。 這使您可以使用后台線程從外部源(如數據庫)接收數據,並在UI線程上顯示數據。 通過使用另一個線程來修改集合,您的用戶界面仍然可以響應用戶交互。
這可以通過在BindingOperations
類上使用靜態方法EnableCollectionSynchronization來完成。
如果要收集或修改大量數據,則可能需要使用后台線程來收集和修改數據,以便用戶界面對輸入保持反應。 要使多個線程能夠訪問集合,請調用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);
}
}
});
}
}
另見: http :/ 10/ 10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
總結此主題,此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 });
}
}
}
}
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 });
}
}
這適用於使用可能存在此問題的VS 2017發行版的Windows 10版本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
您不需要鎖也不需要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
}
其他解決方案似乎有點過分,您可以使用委托來保持線程同步:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var item in Enumerable.Range(1, 500))
{
App.Current.Dispatcher.Invoke((Action)delegate
{
Items.Add(item);
}
}
});
}
這應該可以正常工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.