Reputation: 97
That might sound like a trivial question, but I couldn't find anything that works online. I'm using PRISM
and I'm one step before I walk away and never go back to this framework. Here's why:
I have pretty ObservableCollection
that basically works if I assign a list to it and forget about it. But that's not the goal of ObservableCollection
, right? It changes.. So, here's the collection:
<DataGrid ItemsSource="{Binding Items, Mode=TwoWay}" AutoGenerateColumns="True" />
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
set { SetProperty(ref _items, value); }
}
So, here goes:
Items = InitializeItems(); // Works great!
Items.Add(new Item() { ItemId = 1 }); // Also works
but then..
for (int i = 1; i < 10; i++)
{
Items.Add(new Item() { ItemId = i });
}
failed.. sometimes, with exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll Additional information: An ItemsControl is inconsistent with its items source.
AddRange()
? Forget it..
Everything is done in separate thread:
Task.Factory.StartNew(() =>
{
Items = InitializeItems(); // Works great!
Items.Add(new Item() { ItemId = 1 }); // Also works
for (int i = 1; i < 10; i++)
{
Items.Add(new Item() { ItemId = i });
}
});
I even created extension method:
public static class ObservableCollectionExtensions
{
public static void AddRange<T>(this ObservableCollection<T> data, List<T> range)
{
if (range == null) throw new ArgumentNullException("range");
foreach (var i in range) data.Add(i);
// How can I force ObservableCollection to update?!
}
}
Ehh.. what am I doing wrong? I'm changing ObservableCollection
. So, everytime I want to add new items, I have to create new collection from old and new ones and assign to ObservableCollection? Because only assign operator works for me :(
Thanks for any help!
Upvotes: 6
Views: 42303
Reputation: 5500
An ItemsControl is inconsistent with its items source
means the the datagrid has detected that the items it is holding don't match those on the source, this happens when you change the source to a new collection with out forcing a refresh on the items control
the easiest way to fix this is to change
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
set { SetProperty(ref _items, value); }
}
to
public ObservableCollection<Item> Items{get;}= new ObservableCollection<Item>();
or if you are not using c#6
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
}
this means that you can't change the collection anymore only its content
if you truly require Multithreading then i would add the following code
private Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
this is vital as you need the CurrentDispatcher at the time the class was created not the one currently calling it
then call
dispatcher.Invoke(()=>Items.Add(item));
as this will ensure that only the thread that created the collection changes it
here is a complete working example
public class VM
{
public VM()
{
AddItems = new DelegateCommand(() => Task.Run(()=>
Parallel.ForEach(
Enumerable.Range(1,1000),
(item) => dispatcher.Invoke(() => Items.Add(item))
))
);
}
public ObservableCollection<int> Items { get; } = new ObservableCollection<int>();
private Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
public DelegateCommand AddItems { get; }
}
with the following xaml
<DockPanel >
<Button DockPanel.Dock="Top" Content="Add" Command="{Binding AddItems, Mode=OneWay}" />
<ListView ItemsSource="{Binding Items}"/>
</DockPanel>
Upvotes: 8
Reputation: 48558
Couple of problems in your code.
a) When working with ObservableCollection
, never initialize it again. Create a single instance and add or remove items from it. So you code becomes.
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
}
OR this (if your VS supports)
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
and for adding items
foreach (var item in InitializeItems()) Items.Add(item);
Items.Add(new Item() { ItemId = 1 });
for (int i = 1; i < 10; i++)
{
Items.Add(new Item() { ItemId = i });
}
b) You said
Everything is done in separate thread:
Never update UI bound properties from Non-UI Threads. For Data fetching you can use Non-UI Threads, but once data is retrieved, add/update data in property on UI Threads only.
Upvotes: 6