Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add items to ObservableCollection?

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!

like image 928
Marvin Law Avatar asked Mar 09 '17 17:03

Marvin Law


1 Answers

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>
like image 158
MikeT Avatar answered Sep 21 '22 22:09

MikeT