6

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!

Marvin Law
  • 97
  • 1
  • 2
  • 7
  • Is it possible having a duplicate `ItemId` of `1` is causing an issue unrelated to `ObservableCollection`? – Cory Nelson Mar 09 '17 at 17:55
  • 1
    When an `ObservableCollection` propery is binded in View, it should be always be updated in UI Thread only. Else it will give `STA Thread` error. – Nikhil Agrawal Mar 09 '17 at 17:56
  • In WPF, PropertyChanged events are automatically marshaled to the UI thread, however this is not the case for INotifyCollectionChanged. If you wish to modify the ObservableCollection, you will have to make sure that your modifications are done on the UI thread or there will be an exception. – Brandon Kramer Mar 09 '17 at 17:57
  • When working with a ObserveableCollection you really should really try to avoid doing `Items =` replacing the entire collection frequently defeats the point of the collection. If you are never going to add or remove items from the existing collection and always replace it you would be better off just using a `List` and populating it and assigning it to Items so it will get picked up. – Scott Chamberlain Mar 09 '17 at 18:02
  • 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 – MikeT Mar 09 '17 at 18:05

2 Answers2

8

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>
MikeT
  • 5,398
  • 3
  • 27
  • 43
  • 1
    I was looking for `dispatcher.Invoke(()=>Items.Add(item));` Thanks. Sorry for the delay accepting your answer. – Marvin Law Mar 15 '17 at 05:06
4

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.

Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208
  • A) is correct. B), not so much. INPC bindings are automatically marshalled onto the UI thread. INCC bindings aren't unfortunately. So you do have to use the Dispatcher to update the OC. –  Mar 09 '17 at 18:41
  • @Will For B if you are using .NET 4.5 or newer you can have it auto marshal too. See [this answer](http://stackoverflow.com/questions/2091988/how-do-i-update-an-observablecollection-via-a-worker-thread) from another question on how to set it up. – Scott Chamberlain Mar 09 '17 at 19:17
  • @ScottChamberlain no, that's how you would always do it--use the dispatcher. Nothing new in 4.5. –  Mar 09 '17 at 20:34
  • 1
    @Will I did not link to the correct thing, i did the question instead of the answer. I meant [this answer](http://stackoverflow.com/a/14602121/80274) and it's explaination of [`BindingOperations.EnableCollectionSynchronization(`](https://msdn.microsoft.com/en-us/library/hh198861.aspx) which is new to .NET 4.5 and lets you not need to use a dispatcher. – Scott Chamberlain Mar 10 '17 at 01:45
  • @ScottChamberlain Nice! Thanks for the info. –  Mar 10 '17 at 13:59